自定义仿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高大点啊 哈哈 当然主要是学习,最后转载记录下!!!恩 极好的,还有就是撩到原博主的别忘记了给我介绍费哈
- 自定义仿QQ主界面选项卡
- 自定义仿QQ主界面选项卡
- 自定义控件之DragLayout仿QQ界面
- ListView自定义Adapter实现仿QQ界面
- 高仿QQ(主界面)
- Android仿QQ主界面
- Android仿QQ主界面
- 高仿QQ主界面
- Qt仿QQ界面,主界面、聊天界面、表情界面
- 精仿QQ设置界面(自定义PreferenceActivity)
- Android开发之精仿QQ设置界面(自定义PreferenceActivity
- android仿qq实现自定义拍照界面CameraView
- Android仿QQ主界面-------完善篇
- Android仿QQ主界面-------完善篇
- Android仿QQ主界面-------完善篇
- Viewpaper Fragment RadioButton 仿QQ主界面
- 仿QQ主界面侧滑
- Fragment应用实战:仿qq主界面
- 数塔取数问题
- [leetcode]229. Majority Element II
- Cloudera Manager 5.9 和 CDH 5.9 离线安装指南及个人采坑填坑记
- Maven 启动报错
- 少的力量,强大到你难以想象
- 自定义仿QQ主界面选项卡
- jQ常用知识点整理
- gym100971
- 如何用Cocos2d-android写一个小游戏
- 设计模式的六种原则
- linux 用户管理
- struts2的method="{1}"
- Smartcard CA智能卡之调试
- Css图标样式库