高仿微信app实战(二)- 自定义View实现底部导航栏

来源:互联网 发布:mac qq怎么上传群文件 编辑:程序博客网 时间:2024/05/29 15:39

高仿微信app实战(二)- 自定义View实现底部导航栏

话不多说,先上效果图
这里写图片描述

本文要实现微信底部导航栏,通过自定义View实现。主界面的切换采用ViewPager+Fragement的方式实现。

一、自定义ColorfulRadioButton

由于要实现切换Tab过程中按钮颜色的渐变效果,所以通过自定义RadioButton有些困难,这里重新自定义一个View-ColorfulRadioButton。
首先写一下设计思路:
1、首先每个Button有两部分构成,分别是上方的icon图标和下方的文字,使用两个画笔分别绘制。
2、再onMesure中计算图标和文字的大小与绘制位置。
3、在onDraw中绘制View。通过两次绘制(第一次为灰色图标,第二次为带颜色和透明度图标)设置PorterDuffXfermode为DistIn实现背景色透明度的变化。不懂的可以参考这篇文章:PorterDuffXfermode(即图像混合模式)详解

(1)自定义View属性

列举一下我们的自定义View需要用户指定的属性

  • Icon图标
  • 底部文字
  • 渐变颜色
  • 字体大小

在value文件夹下attrs中声明这些属性

<?xml version="1.0" encoding="utf-8"?><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>

(2)实现自定义View

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);    }}

(三)自定义ColorfulRadioGroup

为了实现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<ColorfulRadioButton> 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 && position<mButtons.size())            mButtons.get(position).setChecked(true);    }    /**     * 获得当前被选中button的位置     * @return     */    public int getCheckedPosition(){        return mCheckedPosition;    }    /**     * <p>Interface definition for a callback to be invoked when the checked     * radio button changed in this group.</p>     */    public interface OnCheckedChangeListener{        /**         * <p>Called when the checked radio button has changed. When the         * selection is cleared, checkedId is -1.</p>         *         * @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);            }        }    }}

(四)、使用自定义ColorfulButton实现微信底部导航栏

布局文件

ViewPager+ColorfulRadioGroup

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="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">    <android.support.v4.view.ViewPager        android:id="@+id/viewpager_main"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1"></android.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></LinearLayout>

几个回调接口

MainFragmentPagerAdapter.java

/** * 在ViewPager中添加Fragment */public class MainFragmentPagerAdapter extends FragmentPagerAdapter {    List<Fragment> 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项目地址

阅读全文
0 0
原创粉丝点击