个人小作品系列,这次继续介绍自己曾经写的一些“小玩意”,这次就介绍下自己初学安卓时,写的一个简仿QQ吧。

里面都是一些基础知识,但是作为初学者来说,还是可以学到很多东西的。

由于这个涉及到的知识很散,就按照界面一个个说,主要就是一些安卓基础,要是你能耐心看下去的话,肯定都能做到。好啦,开始吧。

目录

1.欢迎界面(handler、Timer的使用等)

2.登录界面(属性动画、editText的清除效果、popupwindow的使用、Gif图片的显示等)

3.注册界面(短信功能的使用、Listview、组件间通信)

4.主界面(fragment的使用、自定义dialog、ExpandLIstView的使用)

5.主界面侧拉栏(自定义HorizontalScroView,动画的高级用法)

6.消息电话按钮切换效果(drawable的使用)

7.自定义Toast

8.特殊图片.9.png图片的使用方法与场景

正文

1.欢迎界面

我最终实现的是这个样子,截的QQ欢迎界面原图,然后用拙劣的PS技术签了个名!(现在感觉有点尬,不过记得当时还是觉得挺有意思的,哈哈),这个界面在持续3秒后,便会自动跳转到登录界面。

放图:

实现方式:

使用单独的一个activity,例如我这里为SplashActivity,然后修改程序的入口为SplashActivity

修改程序入口的方法为,在程序入口的activity下面加上下面这段代码,最终这个activity的申明是这个样子

<activityandroid:name=".Splashactivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter>
</activity>

需要注意的是一个应用程序中,只能有一个activity被申明为程序入口,我想这个应该很容易理解,我就不废话啦。

那么怎么实现界面停留3秒,跳转到登录界面的功能呢?这个实现方式有很多种,但是大致思路都是差不多的,可以handler,也可以用定时器(Timer)。我这里使用的第一种方法,相关代码也很简单,调用postDelayed()方法,根据我们要实现的需求这里使用两个参数的重载方法,第一个参数为实现了Runnable接口的对象,第二个参数为时间延迟,第一个对象中,由于Runnable为一个接口,便需要实现该接口下的run方法,在run方法里的代码,将在第二个参数设定的时间延迟后执行。相关代码如下

new Handler().postDelayed(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubIntent intent = new Intent(Splashactivity.this, QQ.class);startActivity(intent);Splashactivity.this.finish();}}, 3000);

不过既然是作为学习,我们也来学习下Timer类的使用。我当时这里为什么不用Timer实现也是有原因的,Timer是定时器,既然叫做定时器,那么这个东东实现的效果,是每隔一段时间做一些事情,但是我这里的需要是隔一段时间做一遍相应的事件即可,并不需要做很多遍,不过为什么我说Timer类也可以呢?因为timer既然定时了,肯定是可以取消的,我们只需要在事件执行一遍之后,取消该定时器即可,相关代码如下。

Timer timer = new Timer();TimerTask task = new TimerTask() {@Overridepublic void run() {try {Intent intent = new Intent(Splashactivity.this, QQ.class);startActivity(intent);Splashactivity.this.finish();} catch (IOException e) {e.printStackTrace();}}};
timer.schedule(task,0,10000);

当然不要忘了,及时取消定时器哦,在Activity的onStop方法里执行 timer.cancel();

2.登录界面

不过对现在来说,这个界面已经不知道是QQ多少个版本之前的登录界面了,简单的布局就不介绍了,可以在源码里查看

说下这个界面需要注意的地方

(1)界面初次进入,有一个动画效果,gif图里可能看不太清,主要是,登录按钮反复掉下弹起,同时用户名密码输入框整体向上移动,类似浮起的效果,这两段动画同时开始同时停止。

(2)用户名密码输入框,在输如数据后有一个清除的小图标,点击小图标,会清空输入框的内容

(3)用户名输入指定的用户名,头像会显示指定的头像,例如我这里效果是输入942845204,头像自动更改为其它头像

(4)用户名输入框最右侧有一个下拉按钮,单机会弹出历史输入信息,点击历史信息条目,自动在用户名密码框中填入对应的信息

(5)登录的加载效果,按钮点击的变色效果,以及声音效果(,,假装可以听到 声音)

当然这些都是完全按照QQ来模仿的。我们按顺序来一个个实现

1.布局的动画效果,这里使用属性动画实现

首先是按钮的掉下弹起效果,使用属性动画ObjectAnimator,直接放代码吧

// 掉下弹起效果
ObjectAnimator yObjectAnimator = ObjectAnimator.ofFloat(btn1, "y", 0, DpUtils.dip2px(QQ.this,260));
yObjectAnimator.setDuration(2200);
yObjectAnimator.setInterpolator(new BounceInterpolator());
yObjectAnimator.setRepeatCount(0);
yObjectAnimator.start();

这里btn1就是我的登录按钮对象,可以看到核心方法就是ofFloat这个方法,参数解释如下

参数一:要执行动画的Object对象

参数二:执行动画的类型,传入字符串,这个有很多值,例如我这里“y”就表示竖直方向的移动动画,若为“alpha”则为透明度变化的动画,还有一些其他值,就不列举了

参数三和参数四可以看做一组,这里我再这一组里写了两个值,分别代表动画起始y值(即0,表示屏幕最上方),动画结束y值(由于单位是px,这里用工具类转换一下,我这里根据上面控件的高度,设定为260dp)

setDuration方法用于设定动画执行的时间,单位是毫秒,我这里2200代表执行2.2秒

setInterpolator设置执行效果(或者称为加速值),什么意思呢,就是动画在执行的过程中,会有速度上的差异,例如,我可以动画开始时执行速度快,然后慢,然后再快,也可慢-快-慢这样来执行,这里我传入了一个BouncdInterpolartor对象,这个是安卓为我们封装好的一个对象,使用它我们可以直接实现反复掉下弹起的效果。

setRepeatCount设定重复次数,为0,表示只执行一次

最后调用start方法在需要执行的地方调用,开始动画。

2.用户名密码输入框,清除效果

这个效果,现在应该有很多可以直接使用的库了,但是其实自己动手做的话也很简单,自己做的东西有一个好处就是自己想怎么改怎么改,别人的可能因为思路不一样有很多问题,所以自己多动多想,核心代码如下

TextWatcher watcher = new TextWatcher() {@Overridepublic void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {String textname = editText1.getText().toString();if (textname.length() > 0 && editText1.isFocused()) {imageView2.setVisibility(View.VISIBLE);imageView2.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View arg0) {editText1.setText("");imageView2.setVisibility(View.INVISIBLE);}});} else {imageView2.setVisibility(View.INVISIBLE);}if (textname.equals("942845204")) {imageview1.setImageDrawable(getResources().getDrawable(R.drawable.image2));} else {imageview1.setImageDrawable(getResources().getDrawable(R.drawable.image1));}String textpassword = editText2.getText().toString();if (textpassword.length() > 0 && editText2.isFocused()) {imageView3.setVisibility(View.VISIBLE);imageView3.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View arg0) {editText2.setText("");imageView3.setVisibility(View.INVISIBLE);}});} else {imageView3.setVisibility(View.INVISIBLE);}}@Overridepublic void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {}@Overridepublic void afterTextChanged(Editable arg0) {}};editText1.addTextChangedListener(watcher);editText2.addTextChangedListener(watcher);

逻辑应该都能看懂,涉及到的就是TextWatcher,这是一个接口,所以我们需要实现它里面的方法,核心方法是onTextChanged,表示设定了该类的EditText在输入内容变化的时候,无论是输入还是删除字符,都会触发该方法,在这里,要实现我们的效果,主要思想就是根据输入框是否有内容来设定旁边叉叉图标的显示与隐藏,然后我们给叉叉图标设置点击事件清空文本框即可达到效果

3.输入指定的用户名,显示对应的头像

这个相信你把第二点看懂了,这个问题就迎刃而解了

4.下拉按钮,弹出窗口,显示历史登录信息

首先,下拉按钮图标,如果你仔细看的话,根据弹出窗口的显示与隐藏,会展示为不同的状态,这里我使用toggleButton实现,当然,你完全可以用普通的button达到效果,不过趁机学习一下toggleButton的使用,何乐而不为呢,嘿嘿嘿

那么怎么实现不同状态显示不同图标的效果呢,代码如下:

<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:state_checked="true" android:drawable="@drawable/qqlog17"/><item android:state_checked="false" android:drawable="@drawable/qqlog16"/>
</selector>

这是一个放在drawable下的xml文件,其中state_checked为状态,true表示打开,false表示关闭,后面对应的drawable设置该状态下,对应显示的图标,可以写一个xml,不过为了方便,我这里就是一张普通的图片啦,然后不要忘了在布局里给toggleButton指定background属性为对上面的drawable对象哦

5.登录的加载效果

这个效果实现起来也比较简单,就是一张gif动态图的显示,我这里使用的也是弹出窗口,在弹出窗口中,显示一个gif动态图,下方一个textview显示加载中的字样,弹出popupwindow对应的布局代码如下

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" ><LinearLayoutandroid:id="@+id/linearLayout1"android:layout_width="150dp"android:layout_height="wrap_content"android:layout_centerInParent="true"android:background="@drawable/qq_loging_bg"android:orientation="vertical" ><com.ant.liao.GifViewandroid:id="@+id/gifloging"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_margin="10dp" ></com.ant.liao.GifView><TextViewandroid:id="@+id/textview1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_margin="10dp"android:text="登录中..."android:textSize="18dp" /></LinearLayout></RelativeLayout>

其中com.ant.liao.GifView  标签就是对应的gif图片,这个是一个第三方jar包,很多人都在使用,jar包可以在文章末尾项目源码里找到,在activtiy中的相关代码也很简单,如下

GifView gifloging = (GifView) viewloging.findViewById(R.id.gifloging);
gifloging.setGifImage(R.drawable.loging);
gifloging.setShowDimension(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

不过这个jar包还是很强大的,我这里只是使用了最简单的功能,有兴趣的可以看看它的其它Api。

除了上面这些小细节,其实也还有很多其它的细节,例如,登录按钮的点击变色效果,我这里还加了一个声音效果,点击有声音的效果也非常简单,先准备一个音频,最好不要太长,不然测试起来会非常,,,,(你懂的)弄个一秒的音频最好,没有的话也可以在我的源码里找到,然后在res目录下,新建一个raw目录,在raw目录下放入你的音频文件即可

播放代码如下,在登录按钮的点击事件里,调用PlayMusic(R.raw.dang)即可,PalyMusic方法如下

private void PlayMusic(int MusicId) {mediaPlayer = MediaPlayer.create(this, MusicId);mediaPlayer.start();
}

以及左下角无法登陆按钮的点击事件,从左下角慢慢弹出一个对话框

这个也是一个动画的运用,弹出框进入动画代码如下

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" ><scaleandroid:duration="300"android:fromXScale="0"android:fromYScale="0"android:pivotX="0%"android:pivotY="100%"android:toXScale="1.0"android:toYScale="1.0" /></set>

弹出框退出动画如下

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" ><translateandroid:duration="300"android:toYDelta="100%p" /></set>

然后在values下的styles文件下,新添加一个style,代码如下

<style name="mystyle" parent="android:Animation"><item name="android:windowEnterAnimation">@anim/dialog_enter</item><item name="android:windowExitAnimation">@anim/dialog_exit</item></style>

然后创建popupwindow的时候,给popupwindow设置setAnimationStyle(R.style.mystyle)即可

3.注册页面

首先在第一个界面,填写手机号码,左侧有一个按钮可以用来设置手机号码的区号,点击进去是一个listview,然后点击相应的条目,将对应的数据回传给之前的界面并显示,说白了就是两个activity之间如何通信传递数据,由于此处需求非常简单,只需要传递一个String字符串即可,相关传递代码如下

数据发送方activity:

String s = text1.getText().toString() + " "+ text2.getText().toString();
Intent intent= new Intent(QQregister2.this, QQregister.class);
intent.putExtra("data_s", s);
QQregister2.this.startActivity(intent);

接收方activity:

Intent intent = getIntent();
String data = intent.getStringExtra("data_s");
if (data != null) {button3.setText(data);
} else {button3.setText("中国 +86");
}

然后下面还有一个蓝色字体的超链接TextView,这个用到了了一个工具类,用于去除链接文字的下划线,设置链接文字的颜色可以在xml布局的TextView标签下设置textColorLink属性,工具类代码如下

public class URLSpanNoUnderline extends URLSpan {public URLSpanNoUnderline(String url) {super(url);}@Overridepublic void updateDrawState(TextPaint ds) {// TODO Auto-generated method stubsuper.updateDrawState(ds);ds.setUnderlineText(false);}
}

然后在代码里,给TextView设置超链接

textview1 = (TextView) this.findViewById(R.id.textview1);
SpannableString st = new SpannableString("我已阅读并同意使用条款和隐私政策");
st.setSpan(new URLSpanNoUnderline("http://zc.qq.com/phone/agreement_chs.html"), 7, 16,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textview1.setText(st);
textview1.setMovementMethod(LinkMovementMethod.getInstance());

然后再用上面的activity之间通信的方式,将选择的手机号码传递到第二个界面,同时生成一段随机验证码发送给第二个界面,来模拟QQ的短信验证码,随机验证码用Random类生成,这里生成六位随机数字,相关代码如下

Random random = new Random();
int[] mes = new int[6];
for (int i = 0; i < 6; i++) {mes[i] = (int) (random.nextInt(9) + 1);
}

然后就是短信发送的方法了,短信发送用到的类为SmsManager,发送的代码也很简单,如下

SmsManager sm = SmsManager.getDefault();
PendingIntent pi = PendingIntent.getBroadcast(QQregister.this, 0, new Intent(), 0);
sm.sendTextMessage(textnumber, null, message, pi, null);

其中sendTextMessage即为发送短信的方法,第三个参数为发送短信的内容,即我们生成的六位随机码。

4.主界面

主界面主要用到的就是Fragment,底部为一个导航栏,点击会在上方显示对应的fragment,这里对初学者来说,有个坑点,就是fragment在安卓中是有两种的,一个是v4包下的,一个是app包下的,所以,在使用的时候,最好统一,不然即便代码没错,但是因为包的问题会导致闪退。关于这两个包的fragment有什么不同,v4包的兼容性更加好,可以兼容到1.6,而app包下的是在3.0以后才有的,所以为了兼容性,一般选择v4下的,二者用法大同小异,下面以动态这个fragment为例,贴一下相关代码

布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/fragment3"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><ListViewandroid:id="@+id/listview"android:layout_width="match_parent"android:layout_height="wrap_content"android:divider="#ffffff"android:dividerHeight="0dp" /></LinearLayout>

对应的fragment代码如下

package com.hq.myqq;import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;public class FragmentPage3 extends Fragment {private ListView listview;private String[] str = new String[]{"搜索项", "布局项", "空白标签一", "游戏", "看点","京东购物", "阅读", "音乐", "直播", "热门活动", "空白便签二", "附近的群", "吃喝玩乐", "同城服务"};private int[] image = new int[]{R.drawable.rightlisticon1,R.drawable.rightlisticon2, R.drawable.rightlisticon3,R.drawable.rightlisticon4, R.drawable.rightlisticon5,R.drawable.rightlisticon6, R.drawable.rightlisticon7,R.drawable.rightlisticon8, R.drawable.rightlisticon9,R.drawable.rightlisticon10};@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_3, container, false);listview = (ListView) view.findViewById(R.id.listview);BaseAdapter adapter = new BaseAdapter() {@Overridepublic View getView(int arg0, View arg1, ViewGroup arg2) {// TODO Auto-generated method stubLayoutInflater layoutInflater = LayoutInflater.from(getActivity());View view;if (getItemId(arg0) == 0) {view = layoutInflater.inflate(R.layout.fragment_3_searchview, null);view.findViewById(R.id.buttonsearch).setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View arg0) {MainTabActivity ma = (MainTabActivity) getActivity();ma.linesearch();}});} else if (getItemId(arg0) == 1) {view = layoutInflater.inflate(R.layout.fragment_3_label,null);view.findViewById(R.id.textView1).setOnClickListener(new OnClickListener() {@SuppressWarnings("static-access")@Overridepublic void onClick(View v) {CustomToast customToast = new CustomToast(getActivity());customToast.makeText(getActivity(), "好友动态", Toast.LENGTH_SHORT);customToast.setpositionbottom();customToast.show();}});view.findViewById(R.id.textView2).setOnClickListener(new OnClickListener() {@SuppressWarnings("static-access")@Overridepublic void onClick(View v) {CustomToast customToast = new CustomToast(getActivity());customToast.makeText(getActivity(), "附近", Toast.LENGTH_SHORT);customToast.setpositionbottom();customToast.show();}});view.findViewById(R.id.textView3).setOnClickListener(new OnClickListener() {@SuppressWarnings("static-access")@Overridepublic void onClick(View v) {CustomToast customToast = new CustomToast(getActivity());customToast.makeText(getActivity(), "兴趣部落", Toast.LENGTH_SHORT);customToast.setpositionbottom();customToast.show();}});} else if (getItemId(arg0) == 2 || getItemId(arg0) == 10) {view = layoutInflater.inflate(R.layout.label, null);} else {view = layoutInflater.inflate(R.layout.fragment_3_groupview, null);ImageView imageView2 = (ImageView) view.findViewById(R.id.imageview2);imageView2.setImageResource(R.drawable.rightlisticon11);ImageView imageView1 = (ImageView) view.findViewById(R.id.imageview1);if (arg0 < 10) {imageView1.setImageResource(image[arg0 - 3]);} else {imageView1.setImageResource(image[arg0 - 4]);}TextView textView = (TextView) view.findViewById(R.id.textview1);textView.setText(getItem(arg0).toString());}return view;}@Overridepublic long getItemId(int arg0) {return arg0;}@Overridepublic Object getItem(int arg0) {return str[arg0];}@Overridepublic int getCount() {return str.length;}};listview.setAdapter(adapter);return view;}@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);MainTabActivity ma = (MainTabActivity) getActivity();ma.flashtitledongtai();listview.setOnItemClickListener(new OnItemClickListener() {@SuppressWarnings("static-access")@Overridepublic void onItemClick(AdapterView<?> arg0, View arg1, int arg2,long arg3) {if (arg2 == 3 || arg2 == 4 || arg2 == 5 || arg2 == 6|| arg2 == 7 || arg2 == 8 || arg2 == 9 || arg2 == 11|| arg2 == 12 || arg2 == 13) {CustomToast customToast = new CustomToast(getActivity());customToast.makeText(getActivity(), "" + listview.getItemAtPosition(arg2), Toast.LENGTH_SHORT);customToast.setpositionbottom();customToast.show();}}});}
}

在这个fragment中,初学者可能会觉得很奇怪,为什么布局里只有一个ListView,最后展现了那么多东西,这是因为在Listview的getView方法中,可以控制,哪一行显示什么内容,通过arg0这个参数,实际上这个参数的含义是代表列表项的位置,从0开始,通过对这个值进行判断,即可实现不同的列表项的效果。

在Activity中控制其显示与隐藏的也就是底部导航栏中的动态按钮,通过设置点击事件,使用FragmentManager和FragmentTransation即可控制fragment的替换。

在主界面,若点击back键,会有弹出对话框的效果,这是一个自定义对话框。

实现原理是创建一个类,继承自Dialog,然后自定义它的布局,以及相关的点击事件,自定义dialog代码如下

package com.hq.myqq;import android.app.Dialog;
import android.content.Context;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;public class Mydialog extends Dialog implementsView.OnClickListener {private TextView textview1;private TextView textview2;private Button button1;private Button button2;static int width = 300;static int height = 150;public Mydialog(Context context, int layout, int style) {this(context, width, height, layout, style);}public Mydialog(Context context, double width, double height, int layout,int style) {super(context, style);setContentView(layout);initWidgets();// 设置窗口属性Window window = getWindow();WindowManager.LayoutParams params = window.getAttributes();// 设置宽度、高度、密度、对齐方式float density = getDensity(context);params.width = (int) (width * density);params.height = (int) (height * density);params.gravity = Gravity.CENTER;window.setAttributes(params);}public void setT(String string) {textview1.setText(string);}public void setM(String string) {textview2.setText(string);}public void setButtonLeftText(String string) {button1.setText(string);}public void setButtonRightText(String string) {button2.setText(string);}@Overrideprotected void onStop() {super.onStop();}private void initWidgets() {button1 = (Button) findViewById(R.id.button1);button1.setOnClickListener(this);button2 = (Button) findViewById(R.id.button2);button2.setOnClickListener(this);textview1 = (TextView) findViewById(R.id.textview1);textview2 = (TextView) findViewById(R.id.textview2);}public float getDensity(Context context) {Resources res = context.getResources();DisplayMetrics dm = res.getDisplayMetrics();return dm.density;}@Overridepublic void onClick(View v) {// TODO Auto-generated method stubswitch (v.getId()) {case R.id.button1:if (listener != null)listener.onClickOk();break;case R.id.button2:if (listener != null)listener.onClickCancel();break;default:break;}}public void setOnClickBtnListener(OnClickBtnListener listener) {this.listener = listener;}private OnClickBtnListener listener = null;public interface OnClickBtnListener {public void onClickOk();public void onClickCancel();}
}

然后在使用的时候就只需这样

MyDialog dialog = new Mydialog(MainTabActivity.this,R.layout.dialog_layout, R.style.dialogTheme);dialog.setT("系统提示");dialog.setM("返回登录界面?");dialog.setButtonLeftText("确定");dialog.setButtonRightText("取消");dialog.setCanceledOnTouchOutside(true);dialog.show();dialog.setOnClickBtnListener(new Mydialog.OnClickBtnListener() {@Overridepublic void onClickOk() {Intent intent = new Intent(MainTabActivity.this,QQ.class);MainTabActivity.this.startActivity(intent);overridePendingTransition(R.anim.push_right_in,R.anim.push_right_out);}@Overridepublic void onClickCancel() {dialog.cancel();}});

5.主界面侧拉栏

咳咳,这个侧拉的效果,其实是我在慕课网上找的视频学的,核心思想是继承一个HorizontalScroView,然后重写onMeasure、onLayout、onScrollChanged方法,来达到效果,下面是源代码

public class SlidingMenu extends HorizontalScrollView {private LinearLayout mWapper;private ViewGroup mMenu;private ViewGroup mContent;private int mScreenWidth;private int mMenuWidth;// dpprivate int mMenuRightPadding = 50;private boolean once;private boolean isOpen;/*** 未使用自定义属性时,调用* * @param context* @param attrs*/public SlidingMenu(Context context, AttributeSet attrs) {this(context, attrs, 0);}/*** 当使用了自定义属性时,会调用此构造方法* * @param context* @param attrs* @param defStyle*/public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// 获取我们定义的属性TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.SlidingMenu, defStyle, 0);int n = a.getIndexCount();for (int i = 0; i < n; i++) {int attr = a.getIndex(i);switch (attr) {case R.styleable.SlidingMenu_rightPadding:mMenuRightPadding = a.getDimensionPixelSize(attr,(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics()));break;}}a.recycle();WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics outMetrics = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(outMetrics);mScreenWidth = outMetrics.widthPixels;}public SlidingMenu(Context context) {this(context, null);}/*** 设置子View的宽和高 设置自己的宽和高*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (!once) {mWapper = (LinearLayout) getChildAt(0);mMenu = (ViewGroup) mWapper.getChildAt(0);mContent = (ViewGroup) mWapper.getChildAt(1);mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth- mMenuRightPadding;mContent.getLayoutParams().width = mScreenWidth;once = true;}super.onMeasure(widthMeasureSpec, heightMeasureSpec);}/*** 通过设置偏移量,将menu隐藏*/@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (changed) {this.scrollTo(mMenuWidth, 0);}}@Overridepublic boolean onTouchEvent(MotionEvent ev) {int action = ev.getAction();switch (action) {case MotionEvent.ACTION_UP:// 隐藏在左边的宽度int scrollX = getScrollX();if (scrollX >= mMenuWidth / 2) {this.smoothScrollTo(mMenuWidth, 0);isOpen = false;} else {this.smoothScrollTo(0, 0);isOpen = true;}return true;}return super.onTouchEvent(ev);}/*** 打开菜单*/public void openMenu() {if (isOpen)return;this.smoothScrollTo(0, 0);isOpen = true;}public void closeMenu() {if (!isOpen)return;this.smoothScrollTo(mMenuWidth, 0);isOpen = false;}/*** 自己添加的 判断左侧菜单是否打开*/public boolean open() {if (isOpen) {return true;}return false;}/*** 切换菜单*/public void toggle() {if (isOpen) {closeMenu();} else {openMenu();}}/*** 滚动发生时*/@Overridepublic void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);float scale = l * 1.0f / mMenuWidth; // 1 ~ 0/*** 区别1:内容区域1.0~0.7 缩放的效果 scale : 1.0~0.0 0.7 + 0.3 * scale* * 区别2:菜单的偏移量需要修改* * 区别3:菜单的显示时有缩放以及透明度变化 缩放:0.7 ~1.0 1.0 - scale * 0.3 透明度 0.6 ~ 1.0 0.6+* 0.4 * (1- scale) ;* */float rightScale = 0.7f + 0.3f * scale;float leftScale = 1.0f - scale * 0.3f;float leftAlpha = 0.6f + 0.4f * (1 - scale);// 调用属性动画,设置TranslationXViewHelper.setTranslationX(mMenu, mMenuWidth * scale * 0.8f);ViewHelper.setScaleX(mMenu, leftScale);ViewHelper.setScaleY(mMenu, leftScale);ViewHelper.setAlpha(mMenu, leftAlpha);// 设置content的缩放的中心点ViewHelper.setPivotX(mContent, 0);ViewHelper.setPivotY(mContent, mContent.getHeight() / 2);ViewHelper.setScaleX(mContent, rightScale);ViewHelper.setScaleY(mContent, rightScale);}
}

然后在xml布局里直接以标签的形式就可以使用。

6.消息电话按钮切换效果

这个其实就是对drawable这个东西的使用需要比较熟练了,实现原理是,给左右两边两个按钮分别设置两个背景,要注意,给按钮设置圆角时,例如左边按钮,圆角只有左上和坐下两个。想关代码如下

左边按钮的两个drawable代码

选中时

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle" ><!-- 填充的颜色:这里设置背景透明 --><solid android:color="#ff304a" /><!-- 边框的颜色 :不能和窗口背景色一样 --><strokeandroid:width="2dp"android:color="#ff304a" /><!-- 设置按钮的四个角为弧形 --><!-- android:radius 弧形的半径 --><cornersandroid:bottomLeftRadius="5dip"android:bottomRightRadius="0dip"android:topLeftRadius="5dip"android:topRightRadius="0dip" /><!-- padding:Button里面的文字与Button边界的间隔 --><paddingandroid:bottom="5dp"android:left="5dp"android:right="5dp"android:top="5dp" /></shape>

非选中时

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" ><!-- 填充的颜色:这里设置背景透明 --><solid android:color="#00000000" /><!-- 边框的颜色 :不能和窗口背景色一样 --><strokeandroid:width="2dp"android:color="#ff304a" /><!-- 设置按钮的四个角为弧形 --><!-- android:radius 弧形的半径 --><cornersandroid:bottomLeftRadius="5dip"android:bottomRightRadius="0dip"android:topLeftRadius="5dip"android:topRightRadius="0dip" /><!-- padding:Button里面的文字与Button边界的间隔 --><paddingandroid:bottom="5dp"android:left="5dp"android:right="5dp"android:top="5dp" />
</shape>

右边的同理,就不赘述啦,然后在代码中,相应按钮的点击事件设置相应的背景色即可

7.自定义Toast

原理也是和自定义dialog一样,创建一个类,继承Toast,下面是代码

public class CustomToast extends Toast {static Toast toast;public CustomToast(Context context) {super(context);}public static Toast makeText(Context context, CharSequence text, int duration) {toast = new Toast(context);LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);View view = inflater.inflate(R.layout.toast_layout, null);TextView textView = (TextView) view.findViewById(R.id.textview1);textView.setText(text);toast.setView(view);toast.setGravity(Gravity.TOP, 0, 30);toast.setDuration(duration);return toast;}public void show() {toast.show();}public static void setpositioncenter() {toast.setGravity(Gravity.CENTER, 0, 50);}public static void setpositionbottom() {toast.setGravity(Gravity.BOTTOM, 0, 50);}}

8.特殊图片.9.png图片的用法

为什么要讲一下这个呢,因为在某些特殊场景里,可能会遇到图片大小,会根据呈现的文字而自动拉伸,如果你不能理解的话,相信你应该用过QQ,在QQ发送消息的时候,无论你的消息内容有多长,最后背景图片是不是都是正常的,丝毫看不出来有任何的变形,那么这种场景下,是无法用普通图片来实现的,那么就需要用到.9图了,在这个例子中,有一个地方也需要用到.9图,哪里呢,见下图

在这个弹出窗口中,你可看到背景图是一个消息一样的背景图,如果窗口里面的内容增加了一条或者减少了一条,那么如果用固定图片,就会产生拉伸,很丑也很不友好,.9图的作用就来了,.9图的一个特殊地方就是可以为它设置内容区,那么将这个图片作为内容区得时候,拉伸就只会拉伸内容区,其他区域不会变化,那么如何制作.9图呢,看下面

选中任意一张图片,右键,选择Create-9-Patch file便可进入.9的制作界面,如下

在右侧你可以预览图片横向拉伸,和纵向拉伸后的效果,而制作过程也很简单,按住shift键,在图片边缘画上内容区代表的宽度和高度,也就是图中的黑线,两条黑线的交叉区域就是内容区了,如下

然后再拉伸的时候,内容区水平方向和竖直方向均会拉伸,四个角落,没有被阴影触及到的地方,两个方向均不会拉伸

内容区上下两个阴影,是会水平方向拉伸,左右两个阴影会竖直方向拉伸。

有了这样一张图片,我们就不怕内容区的更改而造成图片变形的问题啦!

结语

由于内容很多,加上细节也很多,例如ExpandListView的指示器、底部导航栏的图标点击效果等,这里主要挑了一些重要的列出来,当时学习的时候,觉得写完这个QQ后,对我是十分有帮助的,也希望这篇文章能帮助到更多的人。当然,在实际实现的时候,如果遇到了问题可以去直接看我的源码,也可给我留言,我都会及时回复。

源码下载

源码下载

安卓开发个人小作品(2)- QQ简仿相关推荐

  1. 安卓开发个人小作品(1) - 有声计算器

    大家好,最近才开始写博客,准备先整理一下学习安卓以来,自己做的一些"小玩意",在回顾知识的同时,也希望可以帮助到有需要的开发者们!!嘻嘻,开始吧! 计算器的实现,并不是这个&quo ...

  2. 安卓开发裕语言程序---调用qq收藏作为远程更新实例

    安卓开发&裕语言程序---调用qq收藏作为远程更新实例 在裕语言环境下,做安卓个人开发的过程中,我们时有需要在软件内部实现动态远程公告更新,如果不采用传统方法,而改用QQ收藏来做,那么,如何实 ...

  3. java 仿qq登录界面7.1_安卓开发学习笔记(七):仿写腾讯QQ登录注册界面

    这段代码的关键主要是在我们的相对布局以及线性布局上面,我们首先在总体布局里设置为线性布局,然后再在里面设置为相对布局,这是一个十分常见的XML布局模式. 废话不多说,直接上代码: 一.activity ...

  4. 安卓开发入门小程序!一个本科渣渣是怎么逆袭从咸鱼到Offer收割机的?灵魂拷问

    开头 1.一定要把基本的数据结构,经典的算法,Unix编程,程序编译链接及计算机原理等基础知识扎牢,这些会长远影响你的职业发展. 2. 推荐从C语言入门,不单是因为很多操作系统.网络协议栈开源代码由C ...

  5. 安卓开发笔记(三十三):Android仿写微信发现

    首先我们来看看仿写之后的效果: 看到是这个界面我们首先应该思考这些按钮是怎么做出来的?有了一个整体的思路之后才知道该怎么办.最开始我想的就直接利用button控件上面直接加上png的图片就可以形成一个 ...

  6. 腾讯优测干货精选| 安卓开发新技能Get -常用必备小工具汇总

    文/腾讯公司 陈江峰 优测小优有话说: 移动研发及测试干货×××?腾讯优测-优社区你值得拥有~ 开发同学们都知道,安卓开发路上会碰到很多艰难险阻,一不小心就被KO.这时候,没有新技能傍身怎么行?今天我 ...

  7. 安卓开发小知识 - 3

    内容来源:Android Development Tidbits // No. 3 这是第三次分享安卓开发中的一些小知识点.我们很高兴有这么多人知道了这个有趣的系列,并且对你们通过评论和邮件表达的支持 ...

  8. 安卓开发 登录用户信息缓存_小程序云开发之用户注册登录

    小程序 · 云开发已经上线到现在也已经快有两年了,期间自己也基于云开发发布了几个小程序,总得来说,对于前端开发者来说,确实方便了很多.不用买服务器.域名,不需要搭建数据库.静态存储应用.通过平台提供的 ...

  9. 安卓开发大作业_罗湖小程序开发制作价格低

    罗湖小程序开发制作价格低 深圳市驰骋网络技术有限公司 驰骋网络提供外观结构设计+软硬件+APP+云服务 一站式开发服务 小程序开始走入了人们的视线,由于其独特的便捷性,给我们的生活提供了非常大的帮助, ...

最新文章

  1. ECS 实例网络带宽
  2. 懒人神器 !一个创意十足的 Python 命令行工具
  3. 嵌入式学习笔记-记录系统启动次数
  4. linux格式化文件,无需格式化就可以转换文件系统
  5. 现在python已经更新到哪个版本了-Python 3.8 已发布 你会升级么?
  6. shell脚本应用(二)
  7. 数据结构-编程实现一个双链表的建立,双链表的打印,双链表的测长
  8. 欢乐纪中A组周六赛【2019.6.1】
  9. 超级弹珠游戏(洛谷P2356题题解,Java语言描述)
  10. zookeeper专题:zookeeper集群模式下,leader选举流程分析
  11. BRVAH(BaseRecyclerViewAdapterHelper)详解
  12. 如何把word ppt 思维导图这类文件转化为高清晰度的图片(要干货只看粗体黑字)...
  13. 指数分布的期望和方差推导
  14. 程序员工资待遇,投票!
  15. 转 导入视频格式问题(QuickTime player)
  16. ReDet A Rotation-equivariant Detector for Aerial Object Detection 论文学习
  17. 如何在windows上下载安装zeplin
  18. 光纤熔接机的光纤对准方式
  19. 《中国古代文化常识》读书笔记
  20. JS_强制类型转换_Number

热门文章

  1. css做出圆角矩形边框
  2. win7旗舰版 Internet协议版本4的设置方法 --转载
  3. 这是我所收集的一些工具网站
  4. 美联储缩表对外汇市场有什么影响?汇友必入区
  5. 相关子查询(如何理解相关子查询与父查询间存在的 循环)
  6. Unity SKFramework框架(二十一)、Texture Filter 贴图资源筛选工具
  7. 幼儿园大班下学期工作计划
  8. ad_with_lanelet2 编译问题解决
  9. ES 问题 : too_many_clauses maxClauseCount is set to 1024
  10. OS存储管理——FIFO,LRU,OPT命中率