自定义仿QQ主界面选项卡

来源:互联网 发布:中兴软件有限责任公司 编辑:程序博客网 时间:2024/05/18 07:32

自定义QQ主界面选项卡

抽空学习了下大精子的博客,顺便转载下,不过实在是恶心CSDN没有站门一键转载的功能,烦淫啊!!!当然大家也可以去他那xiao习xiao习。点击跳转到原博客文章

QQ Android版本的效果先贴上来
这里写图片描述

可以看到这个 可爱 的选项卡,其实使用xml布局可以很容易的弄出来,但是博主就带大家封装成一个自定义控件!(看我加黑的地方,哈哈,原博主是单身,大家尽管去撩)

博主实现的效果
这里写图片描述

这速度。。。抱歉哈,博主也不知道为啥这么快。。。。

可以看到,支持的还是挺丰富的,还支持包裹,根据自定义属性tabWidht来计算宽度
其实实现起来很简单,下面博主就带小白们来实现一下,大牛请忽略


分析

问题:

实现上述的效果,如果我们是继承View,那么里面的文字、内边框、圆角效果都要自己绘制出来
而且还要支持字体大小的改变和里面文字的排列,显然这样子的代价太大
在我们平常的xml布局中,如果遇到类似的效果,我们很容易就可以想到线性布局
然后里面放几个文本控件并且使用权重进行等分宽度,所以今天的实现的思路就是这样的,只不过平时我们在xml手写的代码都封装起来!

总体思路

1.自定义控件继承LinearLayout,设置本身为水平
2.读取自定义属性到类中
3.根据所有自定义的属性往LinearLayout中添加TextView控件
4.最后就是被系统给绘制显示出来了(这步没我们的事..系统做)

额外的

1.圆角我们使用背景来实现就可以了,GradientDrawable完全可以胜任
2.添加TextView的点击事件,改变选中的下标,然后调用上述的第三步!
3.提供接口给使用者监听被选中的下标和文本

完工

首先国际惯例,决定继承的父类

这里写图片描述

三个参数的构造函数中就是我们上面说的几个步骤啦


支持的属性及其默认的值

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="XTabHost">        <!--半径-->        <attr name="radius" format="dimension" />        <!--文字大小-->        <attr name="text_size" format="dimension" />        <!--文本选中的颜色-->        <attr name="text_select_color" format="color" />        <!--文本未选中的颜色-->        <attr name="text_unselect_color" format="color" />        <!--tab选中的颜色-->        <attr name="tab_select_color" format="color" />        <!--tab没选中的颜色-->        <attr name="tab_unselect_color" format="color" />        <!--tab间距-->        <attr name="tab_space" format="dimension" />        <!--tab的宽,在包裹的时候用到-->        <attr name="tab_width" format="dimension"/>        <!--tab的高,在包裹的时候用到-->        <attr name="tab_height" format="dimension"/>        <!--整体的背景-->        <attr name="bg" format="color" />        <!--默认显示第几个-->        <attr name="default_index" format="integer" />        <!--显示的文本数组-->        <attr name="src" format="reference" />    </declare-styleable></resources>

对应到类中

 /**     * 自身控件的背景     */    private int backBg = Color.WHITE;    /**     * 没有选中的tab的背景     */    private int unSelectTabBg = Color.BLUE;    /**     * 选中的tab的背景     */    private int selectTabBg = Color.WHITE;    /**     * 一个Tab的宽和高,在自身是包裹的时候会被用到     * -1表示不起作用,计算的时候按照包裹孩子处理     * 80是dp的单位     */    private int tabWidth = 80, tabHeight = -1;    /**     * 未选中的文本的颜色     */    private int unSelectTextColor = Color.WHITE;    /**     * 选中的文本的颜色     */    private int selectTextColor = Color.BLUE;    /**     * 默认的字体大小,sp     */    private int textSize = 16;    /**     * 间距,px     */    private int space = 1;    /**     * 圆角半径,dp     */    private int radius = 0;    /**     * 当前的下标     */    private int curIndex = 1;    /**     * 所有要显示的文本     */    private String[] textArr = new String[]{};

可以看到这些属性的效果在效果图中博主基本都使用出来了


读取自定义属性

 /**     * 读取自定义属性     *     * @param context     * @param attrs     */    private void readAttr(Context context, AttributeSet attrs) {        TypedArray a = context.obtainStyledAttributes(attrs,                R.styleable.XTabHost);        //获取自定义属性        curIndex = (int) a.getInt(R.styleable.XTabHost_default_index, 0);        radius = a.getDimensionPixelSize(R.styleable.XTabHost_radius, dpToPx(radius));        backBg = a.getColor(R.styleable.XTabHost_bg, Color.WHITE);        unSelectTabBg = a.getColor(R.styleable.XTabHost_tab_unselect_color,         Color.parseColor("#51B5EF"));        selectTabBg = a.getColor(R.styleable.XTabHost_tab_select_color, Color.WHITE);        Color.WHITE);        Color.parseColor("#51B5EF"));        textSize = a.getDimensionPixelSize(R.styleable.XTabHost_text_size, 16);        space = a.getDimensionPixelSize(R.styleable.XTabHost_tab_space, 1);        tabWidth = a.getDimensionPixelSize(R.styleable.XTabHost_tab_width,         dpToPx(tabWidth));        tabHeight = a.getDimensionPixelSize(R.styleable.XTabHost_tab_height, -1);        CharSequence[] arr = a.getTextArray(R.styleable.XTabHost_src);        if (arr != null) {            String[] tArr = new String[arr.length];            for (int i = 0; i < arr.length; i++) {                tArr[i] = String.valueOf(arr[i]);            }            textArr = tArr;        }        a.recycle();    }

由于每一个读取的属性在上面的定义的时候都有注释,就不做解释了


根据支持的属性生成效果

  /**     * 根据所有的参数,弄出效果     */    private void sove() {        GradientDrawable dd = new GradientDrawable();        //设置圆角        dd.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});        //设置背景颜色        dd.setColor(backBg);        //兼容低版本        if (Build.VERSION.SDK_INT >= 16) {            setBackground(dd);        } else {            setBackgroundDrawable(dd);        }        //移除所有的孩子        removeAllViews();        if (curIndex >= textArr.length || curIndex < 0) {            curIndex = 0;        }        for (int i = 0; i < textArr.length; i++) {            //创建一个文本            TextView tv = new TextView(getContext());            //创建文本的布局对象            LayoutParams params = new LayoutParams(                    0, ViewGroup.LayoutParams.MATCH_PARENT            );            if (i > 0) {                params.leftMargin = space;            }            GradientDrawable d = getFitGradientDrawable(i);            //如果选中了设置选中的颜色和背景            if (curIndex == i) {                tv.setTextColor(selectTextColor);                d.setColor(selectTabBg);            } else {                tv.setTextColor(unSelectTextColor);                d.setColor(unSelectTabBg);            }            //设置文本            tv.setText(textArr[i]);            //设置文本显示在中间            tv.setGravity(Gravity.CENTER);            //设置文本大小            tv.setTextSize(textSize);            //设置文本的背景,兼容低版本            if (Build.VERSION.SDK_INT >= 16) {                tv.setBackground(d);            } else {                //noinspection deprecation                tv.setBackgroundDrawable(d);            }            //设置文本(也就是tab)的权重            params.weight = 1;            tv.setLayoutParams(params);            tv.setTag(i);            tv.setOnClickListener(this);            //添加孩子            addView(tv);        }    }
  /**     * 获取每一个tab的背景图,也就是TextView的背景图,最左边是左边有圆角效果的     * 最右边是右边有圆角效果的     * 即是左边又是右边的是四个角都有圆角的     *     * @param index tab的下标     * @return     */    private GradientDrawable getFitGradientDrawable(int index) {        GradientDrawable d = null;        //根据下标决定圆角        if (index == 0 && index == textArr.length - 1) {//如果只有一个的时候            d = new GradientDrawable();            //设置圆角            d.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});        } else if (index == 0) { //如果是最左边的,那左上角和左下角是圆角            d = new GradientDrawable();            //设置圆角            d.setCornerRadii(new float[]{radius, radius, 0, 0, 0, 0, radius, radius});        } else if (index == textArr.length - 1) {//如果是最右边的,那右上角和右下角是圆角            d = new GradientDrawable();            //设置圆角            d.setCornerRadii(new float[]{0, 0, radius, radius, radius, radius, 0, 0});        } else { //如果是中间的,那么没有圆角            d = new GradientDrawable();            //设置圆角            d.setCornerRadii(new float[]{0, 0, 0, 0, 0, 0, 0, 0});        }        return d;    }

这段代码重点说一下

第一段长的方法sove是我们上面分析实现流程中的第三步,根据所有的属性实现效果
其实很简单,首先移除所有的孩子,然后根据文字的数组的个数,添加N个TextView,让每
一个TextView都是宽度平分父容器,高度填充父容器,这和自己在xml中写的效果是一样的
添加的过程中我们需要判断当前的Tab是否是带有圆角的,因为我们可以看到效果图中
左边的有圆角,右边的也有,所以方法getFitGradientDrawable(int index);
就是用来获取指定下标的背景,其实就是获取每一个TextView应该使用的背景
在for循环开始前我们可以看到我们也设置了本身的背景,本身的背景是四个角都有的哦
然后代码的最后再添加每一个TextView的点击事件,然后切换下选中的TextView和取消选中
的TextView的效果即可

剩下的代码

@Override    public void onClick(View v) {        //拿到下标        int index = (int) v.getTag();        //如果点击的是同一个,不做处理        if (index == curIndex) {            return;        }        //拿到当前的TextView        TextView tv = (TextView) getChildAt(curIndex);        //设置为没有被选中的文本和没有被选中的背景        tv.setTextColor(unSelectTextColor);        GradientDrawable d = getFitGradientDrawable(curIndex);        d.setColor(unSelectTabBg);        if (Build.VERSION.SDK_INT >= 16) {            tv.setBackground(d);        } else {            //noinspection deprecation            tv.setBackgroundDrawable(d);        }        //记录被选中的下标        curIndex = index;        //拿到当前被选中的TextView        tv = (TextView) getChildAt(curIndex);        //设置为被选中的文本和被选中的背景        tv.setTextColor(selectTextColor);        d = getFitGradientDrawable(curIndex);        d.setColor(selectTabBg);        if (Build.VERSION.SDK_INT >= 16) {            tv.setBackground(d);        } else {            //noinspection deprecation            tv.setBackgroundDrawable(d);        }        //如果使用者监听了就通知一下        if (mOnSelectListener != null) {            mOnSelectListener.onSelect(index, textArr[index]);        }    }    /**     * dp的单位转换为px的     *     * @param dps     * @return     */    int dpToPx(int dps) {        return Math.round(getResources().getDisplayMetrics().density * dps);    }    /**     * sp转px     *     * @param spVal     * @return     */    int spToPx(float spVal) {        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,                spVal, getResources().getDisplayMetrics());    }    private OnSelectListener mOnSelectListener;    /**     * 设置监听     *     * @param mOnSelectListener     */    public void setOnSelectListener(OnSelectListener mOnSelectListener) {        this.mOnSelectListener = mOnSelectListener;    }    /**     * 回调接口     */    public interface OnSelectListener {        /**         * 回调方法         *         * @param index         * @param text         */        void onSelect(int index, String text);    }

下面贴出所有的代码

/** * Created by cxj on 2017/2/19. * 模仿qq主界面的选项卡 */public class XTabHost extends LinearLayout implements View.OnClickListener {    public XTabHost(Context context) {        this(context, null);    }    public XTabHost(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public XTabHost(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        //设置孩子排列的方向是水平的        setOrientation(HORIZONTAL);        //读取自定义属性        readAttr(context, attrs);        //显示效果        sove();    }    /**     * 读取自定义属性     *     * @param context     * @param attrs     */    private void readAttr(Context context, AttributeSet attrs) {        TypedArray a = context.obtainStyledAttributes(attrs,                R.styleable.XTabHost);        //获取自定义属性        curIndex = (int) a.getInt(R.styleable.XTabHost_default_index, 0);        radius = a.getDimensionPixelSize(R.styleable.XTabHost_radius, dpToPx(radius));        backBg = a.getColor(R.styleable.XTabHost_bg, Color.WHITE);        unSelectTabBg = a.getColor(R.styleable.XTabHost_tab_unselect_color, Color.parseColor("#51B5EF"));        selectTabBg = a.getColor(R.styleable.XTabHost_tab_select_color, Color.WHITE);        unSelectTextColor = a.getColor(R.styleable.XTabHost_text_unselect_color, Color.WHITE);        selectTextColor = a.getColor(R.styleable.XTabHost_text_select_color, Color.parseColor("#51B5EF"));        textSize = a.getDimensionPixelSize(R.styleable.XTabHost_text_size, 16);        space = a.getDimensionPixelSize(R.styleable.XTabHost_tab_space, 1);        tabWidth = a.getDimensionPixelSize(R.styleable.XTabHost_tab_width, dpToPx(tabWidth));        tabHeight = a.getDimensionPixelSize(R.styleable.XTabHost_tab_height, -1);        CharSequence[] arr = a.getTextArray(R.styleable.XTabHost_src);        if (arr != null) {            String[] tArr = new String[arr.length];            for (int i = 0; i < arr.length; i++) {                tArr[i] = String.valueOf(arr[i]);            }            textArr = tArr;        }        a.recycle();    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //获取计算模式        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);        //获取推荐的宽和高        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);        if (modeWidth == MeasureSpec.EXACTLY) { //如果是确定的        } else { //如果是包裹的或者在横向的列表中            if (tabWidth > -1) {                for (int i = 0; i < getChildCount(); i++) {                    TextView view = (TextView) getChildAt(i);                    LayoutParams lp = (LayoutParams) view.getLayoutParams();                    lp.width = tabWidth;                }            }        }        if (modeHeight == MeasureSpec.EXACTLY) { //如果是确定的        } else { //如果是包裹的或者在纵向的列表中            if (tabHeight > -1) {                for (int i = 0; i < getChildCount(); i++) {                    TextView view = (TextView) getChildAt(i);                    LayoutParams lp = (LayoutParams) view.getLayoutParams();                    lp.height = tabHeight;                }            }        }        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }    /**     * 自身控件的背景     */    private int backBg = Color.WHITE;    /**     * 没有选中的tab的背景     */    private int unSelectTabBg = Color.BLUE;    /**     * 选中的tab的背景     */    private int selectTabBg = Color.WHITE;    /**     * 一个Tab的宽和高,在自身是包裹的时候会被用到     * -1表示不起作用,计算的时候按照包裹孩子处理     * 80是dp的单位     */    private int tabWidth = 80, tabHeight = -1;    /**     * 未选中的文本的颜色     */    private int unSelectTextColor = Color.WHITE;    /**     * 选中的文本的颜色     */    private int selectTextColor = Color.BLUE;    /**     * 默认的字体大小,sp     */    private int textSize = 16;    /**     * 间距,px     */    private int space = 1;    /**     * 圆角半径,dp     */    private int radius = 0;    /**     * 当前的下标     */    private int curIndex = 1;    /**     * 所有要显示的文本     */    private String[] textArr = new String[]{};    /**     * 根据所有的参数,弄出效果     */    private void sove() {        GradientDrawable dd = new GradientDrawable();        //设置圆角        dd.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});        //设置背景颜色        dd.setColor(backBg);        //兼容低版本        if (Build.VERSION.SDK_INT >= 16) {            setBackground(dd);        } else {            setBackgroundDrawable(dd);        }        //移除所有的孩子        removeAllViews();        if (curIndex >= textArr.length || curIndex < 0) {            curIndex = 0;        }        for (int i = 0; i < textArr.length; i++) {            //创建一个文本            TextView tv = new TextView(getContext());            //创建文本的布局对象            LayoutParams params = new LayoutParams(                    0, ViewGroup.LayoutParams.MATCH_PARENT            );            if (i > 0) {                params.leftMargin = space;            }            GradientDrawable d = getFitGradientDrawable(i);            //如果选中了设置选中的颜色和背景            if (curIndex == i) {                tv.setTextColor(selectTextColor);                d.setColor(selectTabBg);            } else {                tv.setTextColor(unSelectTextColor);                d.setColor(unSelectTabBg);            }            //设置文本            tv.setText(textArr[i]);            //设置文本显示在中间            tv.setGravity(Gravity.CENTER);            //设置文本大小            tv.setTextSize(textSize);            //设置文本的背景,兼容低版本            if (Build.VERSION.SDK_INT >= 16) {                tv.setBackground(d);            } else {                //noinspection deprecation                tv.setBackgroundDrawable(d);            }            //设置文本(也就是tab)的权重            params.weight = 1;            tv.setLayoutParams(params);            tv.setTag(i);            tv.setOnClickListener(this);            //添加孩子            addView(tv);        }    }    /**     * 获取每一个tab的背景图,最左边是左边有圆角效果的     * 最右边是右边有圆角效果的     * 即是左边又是右边的是四个角都有圆角的     *     * @param index tab的下标     * @return     */    private GradientDrawable getFitGradientDrawable(int index) {        GradientDrawable d = null;        //根据下标决定圆角        if (index == 0 && index == textArr.length - 1) {            d = new GradientDrawable();            //设置圆角            d.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});        } else if (index == 0) {            d = new GradientDrawable();            //设置圆角            d.setCornerRadii(new float[]{radius, radius, 0, 0, 0, 0, radius, radius});        } else if (index == textArr.length - 1) {            d = new GradientDrawable();            //设置圆角            d.setCornerRadii(new float[]{0, 0, radius, radius, radius, radius, 0, 0});        } else {            d = new GradientDrawable();            //设置圆角            d.setCornerRadii(new float[]{0, 0, 0, 0, 0, 0, 0, 0});        }        return d;    }    @Override    public void onClick(View v) {        //拿到下标        int index = (int) v.getTag();        //如果点击的是同一个,不做处理        if (index == curIndex) {            return;        }        //拿到当前的TextView        TextView tv = (TextView) getChildAt(curIndex);        //设置为没有被选中的文本和没有被选中的背景        tv.setTextColor(unSelectTextColor);        GradientDrawable d = getFitGradientDrawable(curIndex);        d.setColor(unSelectTabBg);        if (Build.VERSION.SDK_INT >= 16) {            tv.setBackground(d);        } else {            //noinspection deprecation            tv.setBackgroundDrawable(d);        }        //记录被选中的下标        curIndex = index;        //拿到当前被选中的TextView        tv = (TextView) getChildAt(curIndex);        //设置为被选中的文本和被选中的背景        tv.setTextColor(selectTextColor);        d = getFitGradientDrawable(curIndex);        d.setColor(selectTabBg);        if (Build.VERSION.SDK_INT >= 16) {            tv.setBackground(d);        } else {            //noinspection deprecation            tv.setBackgroundDrawable(d);        }        //如果使用者监听了就通知一下        if (mOnSelectListener != null) {            mOnSelectListener.onSelect(index, textArr[index]);        }    }    /**     * dp的单位转换为px的     *     * @param dps     * @return     */    int dpToPx(int dps) {        return Math.round(getResources().getDisplayMetrics().density * dps);    }    /**     * sp转px     *     * @param spVal     * @return     */    int spToPx(float spVal) {        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,                spVal, getResources().getDisplayMetrics());    }    private OnSelectListener mOnSelectListener;    /**     * 设置监听     *     * @param mOnSelectListener     */    public void setOnSelectListener(OnSelectListener mOnSelectListener) {        this.mOnSelectListener = mOnSelectListener;    }    /**     * 回调接口     */    public interface OnSelectListener {        /**         * 回调方法         *         * @param index         * @param text         */        void onSelect(int index, String text);    }}

国际惯例的Demo:源码下载

学习了下,顺便现实下,虽然xml也可以实现,但是这样感jiao高大点啊 哈哈 当然主要是学习,最后转载记录下!!!恩 极好的,还有就是撩到原博主的别忘记了给我介绍费哈

原创粉丝点击