跟着慕课网的教学视频学习了如何制作微信的主界面,因为还有一些地方并没有完全搞懂,所以这里主要是记录下整个制作的过程,方便以后的学习!
效果图如图所示:
实现了点击下面tab切换fragment以及滑动切换tab的功能,同时滑动时,下面tab的icon会实现颜色渐变的效果。
首先是主界面的布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bunschen="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@drawable/tab_bg"
android:orientation="horizontal">
<com.chen.weixin_6_0.ChangeIconColorWithText
android:id="@+id/tab_indicator_one"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
bunschen:Icon="@drawable/ic_menu_start_conversation"
bunschen:color="#FF008901"
bunschen:text="@string/app_name"
bunschen:text_size="12sp"/>
<com.chen.weixin_6_0.ChangeIconColorWithText
android:id="@+id/tab_indicator_two"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
bunschen:Icon="@drawable/ic_menu_friendslist"
bunschen:color="#FF008901"
bunschen:text="@string/tab_contact"
bunschen:text_size="12sp"/>
<com.chen.weixin_6_0.ChangeIconColorWithText
android:id="@+id/tab_indicator_three"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
bunschen:Icon="@drawable/ic_menu_emoticons"
bunschen:color="#FF008901"
bunschen:text="@string/tab_find"
bunschen:text_size="12sp"/>
<com.chen.weixin_6_0.ChangeIconColorWithText
android:id="@+id/tab_indicator_four"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
bunschen:Icon="@drawable/ic_menu_allfriends"
bunschen:color="#FF008901"
bunschen:text="@string/tab_me"
bunschen:text_size="12sp"/>
</LinearLayout>
</LinearLayout>
activity_mian
主界面采用线型布局,上面是自定义的ActionBar,中间内容区域是ViewPager+Fragment,下面的Tab区域是一个横向线型布局,其中每个View都是通过自定义布局实现。
1.自定义ActionBar:
//是更多菜单按钮显示出来
private void setOverflowShowingAlways() {
try {
ViewConfiguration config = ViewConfiguration.get(this);
Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
menuKeyField.setAccessible(true);
menuKeyField.setBoolean(config, false);
} catch (Exception e) {
e.printStackTrace();
}
}
该段是通过反射机制,将OverflowButton显示出来,因为在有菜单实体按键的手机中,屏幕中的菜单选项不会显示出来。
1 @Override
2 public boolean onMenuOpened(int featureId, Menu menu) {
3 if (featureId == Window.FEATURE_ACTION_BAR && menu != null) {
4 if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
5 try {
6 Method m = menu.getClass().getDeclaredMethod(
7 "setOptionalIconsVisible", Boolean.TYPE);
8 m.setAccessible(true);
9 m.invoke(menu, true);
10 } catch (Exception e) {
11 }
12 }
13 }
14 return super.onMenuOpened(featureId, menu);
15 }
这段也是通过反射机制将Overflow菜单展开的菜单选项中将图标也显示出来,因为默认是将Overflow菜单展开的菜单选项的突变隐藏掉的。
菜单布局:
1 <menu xmlns:android="http://schemas.android.com/apk/res/android">
2 <item
3 android:id="@+id/action_settings"
4 android:actionViewClass="android.widget.SearchView"
5 android:icon="@drawable/actionbar_search_icon"
6 android:showAsAction="ifRoom|collapseActionView"
7 android:title="@string/action_search"
8 />
9
10 <item
11 android:id="@+id/menu_contact"
12 android:icon="@drawable/menu_group_chat_icon"
13 android:title="@string/menu_contact"/>
14
15 <item
16 android:id="@+id/menu_add_friend"
17 android:icon="@drawable/menu_add_icon"
18 android:title="@string/menu_add_friend"/>
19
20 <item
21 android:id="@+id/menu_scan"
22 android:icon="@drawable/men_scan_icon"
23 android:title="@string/menu_contact"/>
24
25 <item
26 android:id="@+id/menu_feedback"
27 android:icon="@drawable/menu_feedback_icon"
28 android:title="@string/menu_feedback"/>
29
30 </menu>
接下来最主要的就是自定义View
首先是定义自定义的View需要的一些属性
values/attrs.xml:
1 <?xml version="1.0" encoding="utf-8"?>
2 <resources>
3
4 <attr name="Icon" format="reference"></attr>
5 <attr name="color" format="color"></attr>
6 <attr name="text" format="string"></attr>
7 <attr name="text_size" format="dimension"></attr>
8
9 <declare-styleable name="ChangeIconColorWithText">
10 <attr name="Icon"></attr>
11 <attr name="color"></attr>
12 <attr name="text"></attr>
13 <attr name="text_size"></attr>
14 </declare-styleable>
15 </resources>
然后是在布局文件中使用:
1 <com.chen.weixin_6_0.ChangeIconColorWithText
2 android:id="@+id/tab_indicator_one"
3 android:layout_width="0dp"
4 android:layout_height="match_parent"
5 android:layout_weight="1"
6 bunschen:Icon="@drawable/ic_menu_start_conversation"
7 bunschen:color="#FF008901"
8 bunschen:text="@string/app_name"
9 bunschen:text_size="12sp"/>
注意这里的自定义的命名空间:
bunschen:Icon="@drawable/ic_menu_start_conversation"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bunschen="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
这里在开头自定义了命名空间,所以可以使用自定义的属性。
然后就是在构造函数中获取View:
1 public class ChangeIconColorWithText extends View {
2
3 private int mColor = 0xFF008901;
4 private Bitmap mIconBitmap;
5 private String mText = "微信";
6 private int mTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
7 12, getResources().getDisplayMetrics());
8
9 private Bitmap mBitmap;
10 private Canvas mCanvas;
11 private Paint mPaint;
12 private float alpha;
13 private Rect mTextBounds;
14 private Rect mBitmapBounds;
15 private Paint textPaint;
16
17 private final static String INSTANCE_STATUS = "instance_status";
18 private final static String ALPHA_STATUS = "alpha_status";
19
20 public ChangeIconColorWithText(Context context) {
21 this(context, null);
22 }
23
24 public ChangeIconColorWithText(Context context, AttributeSet attrs) {
25 this(context, attrs, 0);
26 }
27
28 public ChangeIconColorWithText(Context context, AttributeSet attrs, int defStyleAttr) {
29 super(context, attrs, defStyleAttr);
30 //获取到布局文件中定义的自定义控件的属性
31 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ChangeIconColorWithText);
32 int n = typedArray.getIndexCount();
33 //将这些属性赋值给该控件的成员变量
34 for (int i = 0; i < n; i++) {
35 int attr = typedArray.getIndex(i);
36 switch (attr) {
37 case R.styleable.ChangeIconColorWithText_color:
38 mColor = typedArray.getColor(attr, 0xFF0E4010);
39 break;
40 case R.styleable.ChangeIconColorWithText_Icon:
41 BitmapDrawable drawable = (BitmapDrawable) typedArray.getDrawable(attr);
42 mIconBitmap = drawable.getBitmap();
43 break;
44 case R.styleable.ChangeIconColorWithText_text:
45 mText = typedArray.getString(attr);
46 break;
47 case R.styleable.ChangeIconColorWithText_text_size:
48 mTextSize = (int) typedArray.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
49 12, getResources().getDisplayMetrics()));
50 break;
51 }
52 }
53 //回收掉使用的资源
54 typedArray.recycle();
55
56 mTextBounds = new Rect();
57 textPaint = new Paint();
58 textPaint.setTextSize(mTextSize);
59 textPaint.setColor(0xff555555);
60 textPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
61 }
62
63 @Override
64 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
65 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
66 //测量图标的宽度,长度与宽度一致
67 int iconWidth = Math.min(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
68 getMeasuredHeight() - getPaddingBottom() - getPaddingTop() - mTextBounds.height());
69 //测量图标绘制的位置的上下左右的值
70 int left = (getMeasuredWidth() - iconWidth)/2;
71 int top = (getMeasuredHeight() - mTextBounds.height() - iconWidth)/2;
72 //确定icon绘制的边界
73 mBitmapBounds = new Rect(left,top,left+iconWidth,top+iconWidth);
74 }
75
76 @Override
77 protected void onDraw(Canvas canvas) {
78 super.onDraw(canvas);
79 //绘制出原无颜色的图标
80 canvas.drawBitmap(mIconBitmap,null,mBitmapBounds,null);
81 //ceil() 方法执行的是向上取整计算,它返回的是大于或等于函数参数,并且与之最接近的整数
82 int Alpha = (int) Math.ceil(255 * alpha);
83 // 内存去准备mBitmap , setAlpha , 纯色 ,xfermode , 图标
84 setupTargetBitmap(Alpha);
85 //1.绘制原文本。
86 setupSourceText(canvas,Alpha);
87 //2.绘制变色文本
88 setupTargetText(canvas,Alpha);
89 //将内存中绘制出的Bitmap对象绘制出来
90 canvas.drawBitmap(mBitmap,0,0,null);
91 }
92 //绘制带颜色的文本
93 private void setupTargetText(Canvas canvas, int alpha) {
94 textPaint.setColor(mColor);
95 textPaint.setAlpha(alpha);
96 //计算文本绘制的位置
97 float x = (getMeasuredWidth() - mTextBounds.width())/2;
98 float y = (mBitmapBounds.bottom + mTextBounds.height());
99 canvas.drawText(mText,x,y,textPaint);
100 }
101 //绘制原文本
102 private void setupSourceText(Canvas canvas, int alpha) {
103 textPaint.setAlpha(255 - alpha);
104 textPaint.setColor(0xff333333);
105 float x = (getMeasuredWidth() - mTextBounds.width())/2;
106 float y = (mBitmapBounds.bottom + mTextBounds.height());
107 canvas.drawText(mText,x,y,textPaint);
108 }
109 //在内存中绘制出icon
110 private void setupTargetBitmap(int alpha) {
111 mBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
112 Bitmap.Config.ARGB_8888);
113 mCanvas = new Canvas(mBitmap);
114 mPaint = new Paint();
115 mPaint.setColor(mColor);
116 mPaint.setAntiAlias(true);
117 mPaint.setDither(true);
118 mPaint.setAlpha(alpha);
119 mCanvas.drawRect(mBitmapBounds, mPaint);
120 //设置显示纯色区域与图标的交集区域,即显示的是图标以及颜色为纯色区域的颜色
121 mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
122 mPaint.setAlpha(255);
123 mCanvas.drawBitmap(mIconBitmap, null, mBitmapBounds, mPaint);
124 }
125 //设置alpha值
126 public void setAlphaView(float alpha){
127 this.alpha = alpha;
128 invalidateView();
129 }
130 //当alpha值变化时,重绘视图
131 private void invalidateView() {
132 //判断是否是在UI线程
133 if(Looper.getMainLooper() == Looper.myLooper()){
134 invalidate();
135 }else{
136 postInvalidate();
137 }
138 }
139 //保存数据值及状态,防止Activity被系统销毁时在回到主界面时显示不正常的现象
140 @Override
141 protected Parcelable onSaveInstanceState() {
142 Bundle bundle = new Bundle();
143 bundle.putParcelable(INSTANCE_STATUS,super.onSaveInstanceState());
144 bundle.putFloat(ALPHA_STATUS,alpha);
145 return bundle;
146 }
147 //回复原先保存的数据值及状态
148 @Override
149 protected void onRestoreInstanceState(Parcelable state) {
150 if(state instanceof Bundle){
151 Bundle bundle = (Bundle) state;
152 alpha = bundle.getFloat(ALPHA_STATUS);
153 super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATUS));
154 return;
155 }
156 super.onRestoreInstanceState(state);
157 }
158 }
然后是在MainActivity中实现滑动更新tab,以及点击tab更新fragment的逻辑:
1 public class MainActivity extends FragmentActivity implements View.OnClickListener, ViewPager.OnPageChangeListener {
2
3 private ViewPager viewPager;
4 //fragment中显示的文本内容
5 private String[] mTitles = new String[]{"first tab fragment", "second tab fragment",
6 "third tab fragment", "fourth tab fragment"};
7
8 private FragmentPagerAdapter mAdapter;
9
10 private List<Fragment> mData = new ArrayList<>();
11 //管理四个tab的List集合
12 private List<ChangeIconColorWithText> tabList = new ArrayList<>();
13
14 @Override
15 protected void onCreate(Bundle savedInstanceState) {
16 super.onCreate(savedInstanceState);
17 setContentView(R.layout.activity_main);
18 setOverflowShowingAlways();
19 getActionBar().setDisplayHomeAsUpEnabled(false);
20
21 initView();
22 initData();
23 viewPager.setAdapter(mAdapter);
24 viewPager.setOnPageChangeListener(this);
25 }
26
27 private void initData() {
28 for(String title : mTitles){
29 TabFragment tabFragment = new TabFragment();
30 Bundle bundle = new Bundle();
31 bundle.putString(TabFragment.TITLE,title);
32 tabFragment.setArguments(bundle);
33 mData.add(tabFragment);
34 }
35
36 mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
37 @Override
38 public Fragment getItem(int position) {
39 return mData.get(position);
40 }
41
42 @Override
43 public int getCount() {
44 return mData.size();
45 }
46 };
47 }
48
49 private void initView() {
50 viewPager = (ViewPager) findViewById(R.id.viewPager);
51 ChangeIconColorWithText one = (ChangeIconColorWithText) findViewById(R.id.tab_indicator_one);
52 tabList.add(one);
53 ChangeIconColorWithText two = (ChangeIconColorWithText) findViewById(R.id.tab_indicator_two);
54 tabList.add(two);
55 ChangeIconColorWithText three = (ChangeIconColorWithText) findViewById(R.id.tab_indicator_three);
56 tabList.add(three);
57 ChangeIconColorWithText four = (ChangeIconColorWithText) findViewById(R.id.tab_indicator_four);
58 tabList.add(four);
59
60 one.setOnClickListener(this);
61 two.setOnClickListener(this);
62 three.setOnClickListener(this);
63 four.setOnClickListener(this);
64 resetOtherTab();
65 one.setAlphaView(1);
66 }
67
68
69 @Override
70 public boolean onCreateOptionsMenu(Menu menu) {
71 getMenuInflater().inflate(R.menu.menu_main, menu);
72
73 return true;
74 }
75
76 @Override
77 public boolean onOptionsItemSelected(MenuItem item) {
78 int id = item.getItemId();
79
80 if (id == R.id.action_settings) {
81 return true;
82 }
83 return super.onOptionsItemSelected(item);
84 }
85
86 //是更多菜单按钮显示出来
87 private void setOverflowShowingAlways() {
88 try {
89 ViewConfiguration config = ViewConfiguration.get(this);
90 Field menuKeyField = ViewConfiguration.class
91 .getDeclaredField("sHasPermanentMenuKey");
92 menuKeyField.setAccessible(true);
93 menuKeyField.setBoolean(config, false);
94 } catch (Exception e) {
95 e.printStackTrace();
96 }
97 }
98
99 @Override
100 public boolean onMenuOpened(int featureId, Menu menu) {
101 if (featureId == Window.FEATURE_ACTION_BAR && menu != null) {
102 if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
103 try {
104 Method m = menu.getClass().getDeclaredMethod(
105 "setOptionalIconsVisible", Boolean.TYPE);
106 m.setAccessible(true);
107 m.invoke(menu, true);
108 } catch (Exception e) {
109 }
110 }
111 }
112 return super.onMenuOpened(featureId, menu);
113 }
114
115 @Override
116 public void onClick(View v) {
117 resetOtherTab();
118 switch(v.getId()){
119 case R.id.tab_indicator_one:
120 tabList.get(0).setAlphaView(1);
121 viewPager.setCurrentItem(0,false);
122 break;
123 case R.id.tab_indicator_two:
124 tabList.get(1).setAlphaView(1);
125 viewPager.setCurrentItem(1,false);
126 break;
127 case R.id.tab_indicator_three:
128 tabList.get(2).setAlphaView(1);
129 viewPager.setCurrentItem(2,false);
130 break;
131 case R.id.tab_indicator_four:
132 tabList.get(3).setAlphaView(1);
133 viewPager.setCurrentItem(3,false);
134 break;
135 }
136 }
137
138 private void resetOtherTab() {
139 for(int i = 0; i < tabList.size(); i++){
140 tabList.get(i).setAlphaView(0);
141 }
142 }
143 //这里是在ViewPager滑动时,因为只有两个tab的颜色会发生变化,所以通过将他们的icon和文本颜色的alpha值进行改变,从而产生渐变的效果。
144 @Override
145 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
146 if(positionOffset > 0){
147 ChangeIconColorWithText left = tabList.get(position);
148 ChangeIconColorWithText right = tabList.get(position + 1);
149
150 left.setAlphaView(1-positionOffset);
151 right.setAlphaView(positionOffset);
152 }
153 }
154
155 @Override
156 public void onPageSelected(int position) {
157
158 }
159
160 @Override
161 public void onPageScrollStateChanged(int state) {
162
163 }
fragment:
1 public class TabFragment extends Fragment {
2
3 private static String mTitle = "default";
4 public static final String TITLE = "title";
5 @Override
6 public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState) {
7 TextView tv = new TextView(getActivity());
8 if(getArguments() != null) {
9 mTitle = getArguments().getString(TITLE);
10 }
11 tv.setText(mTitle);
12 tv.setTextSize(20);
13 tv.setTextColor(Color.BLACK);
14 tv.setGravity(Gravity.CENTER);
15 return tv;
16 }
17 }
基本内容就是这些,其中自定义View是难点,主要是自定义View中的绘制方法,XferMode的DST_IN方法。这里记录下来,以后慢慢学习。