本文要实现微信底部导航栏,通过自定义View实现。主界面的切换采用ViewPager+Fragement的方式实现。
由于要实现切换Tab过程中按钮颜色的渐变效果,所以通过自定义RadioButton有些困难,这里重新自定义一个View-ColorfulRadioButton。
首先写一下设计思路:
1、首先每个Button有两部分构成,分别是上方的icon图标和下方的文字,使用两个画笔分别绘制。
2、再onMesure中计算图标和文字的大小与绘制位置。
3、在onDraw中绘制View。通过两次绘制(第一次为灰色图标,第二次为带颜色和透明度图标)设置PorterDuffXfermode为DistIn实现背景色透明度的变化。不懂的可以参考这篇文章:PorterDuffXfermode(即图像混合模式)详解
列举一下我们的自定义View需要用户指定的属性
在value文件夹下attrs中声明这些属性
<resources>
<declare-styleable name="ColorfulRadioButton">
<attr name="icon" format="reference">attr>
<attr name="color" format="color">attr>
<attr name="text" format="string">attr>
<attr name="text_size" format="dimension">attr>
resources>
package com.example.sdk.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.Checkable;
import android.widget.CompoundButton;
import com.example.sdk.R;
/**
* Created by 韩冰 on 2017/7/12.
*/
public class ColorfulRadioButton extends View implements Checkable{
public ColorfulRadioButton(Context context) {
this(context,null);
}
public ColorfulRadioButton(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
private int mColor = 0xFF45C01A; //默认渐变颜色
private Bitmap mIconBitmap;
private String mText = "Button"; //默认底部显示文字
private int mTextSize = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()); //默认字体大小
private Canvas mCanvas;
private Bitmap mBitmap;
private Paint mPaint;
private float mAlpha = 0; //透明度
private Rect mIconRect; //icon Rect
private Rect mTextBound; //文字Rect
private Paint mTextPaint; //绘制文字画笔
private OnCheckedChangeListener mOnCheckedChangedListener; //监听按钮状态变化
public ColorfulRadioButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ColorfulRadioButton);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
if (attr == R.styleable.ColorfulRadioButton_icon) {
BitmapDrawable drawable = (BitmapDrawable) a.getDrawable(attr);
mIconBitmap = drawable.getBitmap();
} else if (attr == R.styleable.ColorfulRadioButton_color) {
mColor = a.getColor(attr, 0xFF45C01A);
} else if (attr == R.styleable.ColorfulRadioButton_text) {
mText = a.getString(attr);
} else if (attr == R.styleable.ColorfulRadioButton_text_size) {
mTextSize = (int) a.getDimension(attr, TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12,
getResources().getDisplayMetrics()));
}
}
a.recycle();
mTextBound = new Rect();
mTextPaint = new Paint();
mTextPaint.setTextSize(mTextSize);
mTextPaint.setColor(0Xff555555);
mTextPaint.getTextBounds(mText, 0, mText.length(), mTextBound);
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setChecked(true);
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//icon宽度等于view的宽减去padding(或者view的高减去padding再减去文字的高度,取两者的最小值)
int iconWidth = Math.min(getMeasuredWidth() - getPaddingLeft()
- getPaddingRight(), getMeasuredHeight() - getPaddingTop()
- getPaddingBottom() - mTextBound.height());
int left = getMeasuredWidth() / 2 - iconWidth / 2; //icon的左边界坐标
int top = getMeasuredHeight() / 2 - (mTextBound.height() + iconWidth )
/ 2; //icon的右边界坐标
mIconRect = new Rect(left, top, left + iconWidth, top + iconWidth);
}
@Override
protected void onDraw(Canvas canvas)
{
canvas.drawBitmap(mIconBitmap, null, mIconRect, null);
int alpha = (int) Math.ceil(255 * mAlpha);
// 内存去准备mBitmap , setAlpha , 纯色 ,xfermode , 图标
setupTargetBitmap(alpha);
// 1、绘制原文本 ; 2、绘制变色的文本
drawSourceText(canvas, alpha);
drawTargetText(canvas, alpha);
canvas.drawBitmap(mBitmap, 0, 0, null);
}
/**
* 绘制变色的文本
*
* @param canvas
* @param alpha
*/
private void drawTargetText(Canvas canvas, int alpha)
{
mTextPaint.setColor(mColor);
mTextPaint.setAlpha(alpha);
int x = getMeasuredWidth() / 2 - mTextBound.width() / 2;
int y = mIconRect.bottom + mTextBound.height();
canvas.drawText(mText, x, y , mTextPaint);
}
/**
* 绘制原文本
*
* @param canvas
* @param alpha
*/
private void drawSourceText(Canvas canvas, int alpha)
{
mTextPaint.setColor(0xff8D8D8D);
mTextPaint.setAlpha(255 - alpha);
int x = getMeasuredWidth() / 2 - mTextBound.width() / 2;
int y = mIconRect.bottom + mTextBound.height();
canvas.drawText(mText, x, y, mTextPaint);
}
/**
* 在内存中绘制可变色的Icon
*/
private void setupTargetBitmap(int alpha)
{
mBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mPaint = new Paint();
mPaint.setColor(mColor);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setAlpha(alpha);
mCanvas.drawRect(mIconRect, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mPaint.setAlpha(255);
mCanvas.drawBitmap(mIconBitmap, null, mIconRect, mPaint);
}
public void setIconAlpha(float alpha)
{
this.mAlpha = alpha;
invalidateView();
}
/**
* 重绘
*/
private void invalidateView()
{
if (Looper.getMainLooper() == Looper.myLooper())
{
invalidate();
} else
{
postInvalidate();
}
}
@Override
public void setChecked(boolean checked) {
setIconAlpha(checked ? 1:0);
if (mOnCheckedChangedListener != null){
mOnCheckedChangedListener.onCheckedChanged(this, checked);
}
}
@Override
public boolean isChecked() {
return mAlpha == 1;
}
@Override
public void toggle() {
setChecked(!isChecked());
}
public void setmOnCheckedChangedListener(OnCheckedChangeListener listener){
this.mOnCheckedChangedListener = listener;
}
/**
* Interface definition for a callback to be invoked when the checked state
* of a compound button changed.
*/
public static interface OnCheckedChangeListener {
/**
* Called when the checked state of a compound button has changed.
*
* @param buttonView The button view whose state has changed.
* @param isChecked The new checked state of buttonView.
*/
void onCheckedChanged(ColorfulRadioButton buttonView, boolean isChecked);
}
}
为了实现ViewPager滑动时颜色渐变的效果,需要对同一组的Button进行统一管理,这里的实现模仿了android RadioGroup API。这里setAlphaOffset方法是关键,它可以联动相邻的两个button实现颜色渐变效果。
package com.example.sdk.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.IdRes;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.example.sdk.R;
import com.example.sdk.utils.ViewUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Created by 韩冰 on 2017/7/12.
*/
public class ColorfulRadioGroup extends LinearLayout {
public ColorfulRadioGroup(Context context) {
super(context);
setOrientation(HORIZONTAL);
init();
}
private int mCheckedId = -1;
private int mCheckedPosition = -1;
private OnCheckedChangeListener mOnCheckedChangeListener;
public void setOnCheckedChangeListener(OnCheckedChangeListener listener){
this.mOnCheckedChangeListener = listener;
}
private boolean mProtectFromCheckedChange = false;
private ColorfulRadioButton.OnCheckedChangeListener mChildOnCheckedChangeListener; //为child button设置的回调
private HierarchyChangeListener mHierarchyChangeListener; //加入新的button为其设置回调
private List mButtons; //属于这个组的Button数组
public ColorfulRadioGroup(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.ColorfulRadioGroup);
int value = attributes.getResourceId(R.styleable.ColorfulRadioGroup_checkedButton, View.NO_ID);
if (value != View.NO_ID) {
mCheckedId = value;
}
attributes.recycle();
init();
}
private void init(){
mChildOnCheckedChangeListener = new CheckedStateTracker();
mHierarchyChangeListener = new HierarchyChangeListener();
super.setOnHierarchyChangeListener(mHierarchyChangeListener);
mButtons = new ArrayList<>();
}
/**
* {@inheritDoc}
*/
@Override
protected void onFinishInflate() {
//完成加载后设置默认选中的按钮
super.onFinishInflate();
// checks the appropriate radio button as requested in the XML file
if (mCheckedId != -1) {
mProtectFromCheckedChange = true;
setCheckedStateForView(mCheckedId, true);
mProtectFromCheckedChange = false;
setCheckedId(mCheckedId);
}
}
private void setCheckedId(@IdRes int id) {
mCheckedId = id;
mCheckedPosition = mButtons.indexOf(findViewById(mCheckedId));
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, mCheckedPosition);
}
}
private void setCheckedStateForView(int viewId, boolean checked) {
View checkedView = findViewById(viewId);
if (checkedView != null && checkedView instanceof ColorfulRadioButton) {
((ColorfulRadioButton) checkedView).setChecked(checked);
}
}
/**
* //设置透明度,两个button中第一个button的位置为position的值,
* @param position 第一个button的位置
* @param offset 偏移量,取值范围为0-1
*/
public void setAlphaOffset(int position, float offset){
if (offset <= 0 || offset>=1){
return;
}
int index = mCheckedPosition;
if (position == index || position == index-1){
mButtons.get(position).setIconAlpha(1-offset);
mButtons.get(position+1).setIconAlpha(offset);
}
}
/**
* 设置选中button的位置
* @param position
*/
public void setCheckedPosition(int position){
if (position>=0 && positiontrue);
}
/**
* 获得当前被选中button的位置
* @return
*/
public int getCheckedPosition(){
return mCheckedPosition;
}
/**
* Interface definition for a callback to be invoked when the checked
* radio button changed in this group.
*/
public interface OnCheckedChangeListener{
/**
* Called when the checked radio button has changed. When the
* selection is cleared, checkedId is -1.
*
* @param group the group in which the checked radio button has changed
* @param position the unique identifier of the newly checked radio button
*/
void onCheckedChanged(ColorfulRadioGroup group, int position);
}
private class CheckedStateTracker implements ColorfulRadioButton.OnCheckedChangeListener{
/**
* 如果某个按钮被选中,设置之前被选中按钮为被选中状态
* @param buttonView The button view whose state has changed.
* @param isChecked The new checked state of buttonView.
*/
@Override
public void onCheckedChanged(ColorfulRadioButton buttonView, boolean isChecked) {
// prevents from infinite recursion
if (mProtectFromCheckedChange) {
return;
}
mProtectFromCheckedChange = true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
int id = buttonView.getId();
setCheckedId(id);
}
}
/**
*
*/
private class HierarchyChangeListener implements
ViewGroup.OnHierarchyChangeListener {
/**
* 当新的Button被加入时,为button设置id,设置回调,加入buttons数组
*/
public void onChildViewAdded(View parent, View child) {
if (parent == ColorfulRadioGroup.this && child instanceof ColorfulRadioButton) {
int id = child.getId();
// generates an id if it's missing
if (id == View.NO_ID) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
id = ViewUtils.generateViewId();
else
id = View.generateViewId();
child.setId(id);
}
((ColorfulRadioButton) child).setmOnCheckedChangedListener(
mChildOnCheckedChangeListener);
mButtons.add((ColorfulRadioButton) child);
}
}
/**
* 当Button被移除时,移除相应信息
*/
public void onChildViewRemoved(View parent, View child) {
if (parent == ColorfulRadioGroup.this && child instanceof ColorfulRadioButton) {
if (child.getId() == mCheckedId){
mCheckedId = -1;
mCheckedPosition = -1;
}
((ColorfulRadioButton) child).setmOnCheckedChangedListener(null);
mButtons.remove(child);
}
}
}
}
ViewPager+ColorfulRadioGroup
"1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.example.wechat.MainActivity"
android:orientation="vertical">
.support.v4.view.ViewPager
android:id="@+id/viewpager_main"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"> .support.v4.view.ViewPager>
<com.example.sdk.widgets.ColorfulRadioGroup
android:id="@+id/id_toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/navigation_bar_height"
android:orientation="horizontal"
app:checkedButton="@+id/id_indicator_wechat"
>
<com.example.sdk.widgets.ColorfulRadioButton
android:id="@+id/id_indicator_wechat"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:padding="5dp"
app:icon="@drawable/icon_chat"
app:text="@string/navi_wechat"
app:text_size="12sp"
app:color="#ff45c01a" />
<com.example.sdk.widgets.ColorfulRadioButton
android:id="@+id/id_indicator_contact"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:padding="5dp"
app:icon="@drawable/icon_contact"
app:text="@string/navi_contact"
app:text_size="12sp"
app:color="#ff45c01a" />
<com.example.sdk.widgets.ColorfulRadioButton
android:id="@+id/id_indicator_discover"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:padding="5dp"
app:icon="@drawable/icon_discover"
app:text="@string/navi_discover"
app:text_size="12sp"
app:color="#ff45c01a" />
<com.example.sdk.widgets.ColorfulRadioButton
android:id="@+id/id_indicator_me"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:padding="5dp"
app:icon="@drawable/icon_me"
app:text="@string/navi_me"
app:text_size="12sp"
app:color="#ff45c01a" />
com.example.sdk.widgets.ColorfulRadioGroup>
MainFragmentPagerAdapter.java
/**
* 在ViewPager中添加Fragment
*/
public class MainFragmentPagerAdapter extends FragmentPagerAdapter {
List mFragments;
public MainFragmentPagerAdapter(FragmentManager fm) {
super(fm);
mFragments = new ArrayList<>();
init();
}
private void init() {
mFragments.add(new WechatFragment());
mFragments.add(new ContactFragment());
mFragments.add(new DiscoverFragment());
mFragments.add(new MeFragment());
}
@Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
@Override
public int getCount() {
return mFragments.size();
}
}
MainPagerStateChangeListener.java
/**
* 为ViewPager设置回调,在ViewPager进行滑动切换时为ColorfulRadiogroup设置渐变效果
*/
public class MainPagerStateChangeListener implements ViewPager.OnPageChangeListener {
private ColorfulRadioGroup mNaviBar;
public MainPagerStateChangeListener(ColorfulRadioGroup naviBar){
this.mNaviBar = naviBar;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (positionOffset == 0){
return;
}
mNaviBar.setAlphaOffset(position,positionOffset);
Log.i("tag","OnPageScrolled:" + position + positionOffset);
}
@Override
public void onPageSelected(int position) {
if (position != mNaviBar.getCheckedPosition())
mNaviBar.setCheckedPosition(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
}
MainOnNavitabSelectListener
/**
* 为ColorfulRadioGroup 设置回调,当某个Button被选中时,ViewPager切换到相应的Fragment
*/
public class MainOnNavitabSelectListner implements OnCheckedChangeListener {
private ViewPager mViewPager;
public MainOnNavitabSelectListner(ViewPager viewPager){
this.mViewPager = viewPager;
}
@Override
public void onCheckedChanged(ColorfulRadioGroup group, int position) {
mViewPager.setCurrentItem(position,false);
}
}
MainActivity.java
package com.example.wechat;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.View;
import android.widget.RadioGroup;
import com.example.sdk.widgets.ColorfulRadioGroup;
import com.example.wechat.base.BaseActivity;
import com.example.wechat.components.MainFragmentPagerAdapter;
import com.example.wechat.components.MainOnNavitabSelectListner;
import com.example.wechat.components.MainPagerStateChangeListener;
public class MainActivity extends BaseActivity {
private ColorfulRadioGroup mToolbar;
private ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mToolbar = (ColorfulRadioGroup) findViewById(R.id.id_toolbar);
mViewPager = (ViewPager) findViewById(R.id.viewpager_main);
mViewPager.setAdapter(new MainFragmentPagerAdapter(getSupportFragmentManager()));
mViewPager.addOnPageChangeListener(new MainPagerStateChangeListener(mToolbar));
mToolbar.setOnCheckedChangeListener(new MainOnNavitabSelectListner(mViewPager));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return super.onCreateOptionsMenu(menu);
}
}
最后分享一下自己制作的icon资源
微信底部导航栏icon
附项目github地址,欢迎学习交流
github项目地址