仿微信主界面

跟着慕课网的教学视频学习了如何制作微信的主界面,因为还有一些地方并没有完全搞懂,所以这里主要是记录下整个制作的过程,方便以后的学习!

效果图如图所示:

技术分享技术分享

实现了点击下面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 <item11 android:id="@+id/menu_contact"12  android:icon="@drawable/menu_group_chat_icon"13  android:title="@string/menu_contact"/>14 15 <item16 android:id="@+id/menu_add_friend"17  android:icon="@drawable/menu_add_icon"18  android:title="@string/menu_add_friend"/>19 20 <item21 android:id="@+id/menu_scan"22  android:icon="@drawable/men_scan_icon"23  android:title="@string/menu_contact"/>24 25 <item26 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.ChangeIconColorWithText2 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 //在内存中绘制出icon110 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  @Override141 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  @Override149 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  @Override100 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  @Override116 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  @Override145 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  @Override156 public void onPageSelected(int position) {157 158  }159 160  @Override161 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方法。这里记录下来,以后慢慢学习。

相关文章