首先来介绍一下这个自定义View:

  • (1)这个自定义View的名称叫做 GradientTab ,继承自View类;
  • (2)这个自定义View实现了颜色渐变的Tab导航栏(仿微信主菜单),用户在左右滑动的时候,当前页对应的Tab逐渐变淡,目标页的Tab逐渐变深;
  • (3)用户可以在XML布局中自定义变色的颜色、图标、文本、文本大小、文本颜色、图文间隔等属性。

  接下来简单介绍一下在这个自定义View中用到的技术点:

  • (1)自定义属性;
  • (2)在 onMeasure() 方法中对View进行测量;
  • (3)在 onLayout() 方法中进行一些在获取到测量值之后才能进行的操作,避免代码多次调用影响性能;
  • (4)使用 Canvas 、 Paint 、 Bitmap 类对自定义View进行绘制;
  • (5)通过 onSaveInstanceState() 和 onRestoreInstanceState() 方法保存和重置View状态(透明度)。

  下面是这个自定义View—— GradientTab 的实现代码:

  自定义View类 GradientTab.java 中的代码:

  1. import android.content.Context;
  2. import android.content.res.TypedArray;
  3. import android.graphics.Bitmap;
  4. import android.graphics.BitmapFactory;
  5. import android.graphics.Canvas;
  6. import android.graphics.Color;
  7. import android.graphics.Matrix;
  8. import android.graphics.Paint;
  9. import android.graphics.PorterDuff;
  10. import android.graphics.PorterDuffXfermode;
  11. import android.os.Bundle;
  12. import android.os.Parcelable;
  13. import android.support.annotation.Nullable;
  14. import android.util.AttributeSet;
  15. import android.util.TypedValue;
  16. import android.view.View;
  17.  
  18. /**
  19. * 与ViewPager连用的拖动可渐变色的Tab按钮
  20. */
  21. public class GradientTab extends View {
  22. private int width, height; // View最终显示的宽高
  23.  
  24. private int backColor = Color.GREEN; // 自定义属性:Tab按钮的背景颜色
  25. private int spacing = -1; // 自定义属性:Tab按钮中图标和文本的间隔
  26. private StringBuffer text = new StringBuffer(); // 自定义属性:Tab按钮中显示的文本
  27. private int textColor = Color.BLACK; // 自定义属性:Tab按钮中文本的颜色
  28. private int textSize = -1; // 自定义属性:Tab按钮的文本大小
  29.  
  30. private Bitmap tabBitmap; // 绘制元素的Bitmap
  31. private Bitmap tmpBm; // 临时Bitmap
  32. private Paint tabPaint; // 绘制元素的画笔
  33. private Paint iconPaint; // 绘制图标的画笔
  34. private Paint textPaint; // 绘制文本的画笔
  35. private Bitmap iconBm; // 图标图片对应的Bitmap
  36.  
  37. private float iconWidth; // 图标图片的宽度
  38. private float iconHeight; // 图标图片的高度
  39.  
  40. private int alpha = 0; // 透明度
  41.  
  42. public GradientTab(Context context) {
  43. this(context, null);
  44. }
  45.  
  46. public GradientTab(Context context, @Nullable AttributeSet attrs) {
  47. this(context, attrs, 0);
  48. }
  49.  
  50. public GradientTab(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
  51. super(context, attrs, defStyleAttr);
  52. // 加载自定义属性
  53. TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.GradientTab, defStyleAttr, 0);
  54. int count = array.getIndexCount();
  55. for (int i = 0; i < count; i++) {
  56. int attr = array.getIndex(i);
  57. switch (attr) {
  58. case R.styleable.GradientTab_backColor:
  59. backColor = array.getColor(attr, Color.GREEN);
  60. break;
  61. case R.styleable.GradientTab_iconImg:
  62. int iconRes = array.getResourceId(attr, -1); // 自定义属性:Tab按钮中显示的图标的资源ID
  63. if (iconRes != -1) {
  64. iconBm = BitmapFactory.decodeResource(getResources(), iconRes);
  65. iconWidth = iconBm.getWidth();
  66. iconHeight = iconBm.getHeight();
  67. }
  68. break;
  69. case R.styleable.GradientTab_spacing:
  70. textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
  71. array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, context.getResources().getDisplayMetrics())),
  72. context.getResources().getDisplayMetrics());
  73. break;
  74. case R.styleable.GradientTab_text:
  75. text.delete(0, text.length());
  76. text.append(array.getString(attr));
  77. break;
  78. case R.styleable.GradientTab_textColor:
  79. textColor = array.getColor(attr, Color.BLACK);
  80. break;
  81. case R.styleable.GradientTab_textSize:
  82. textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
  83. array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, context.getResources().getDisplayMetrics())),
  84. context.getResources().getDisplayMetrics());
  85. break;
  86. }
  87. }
  88. array.recycle();
  89. // 为一些自定义属性设置默认值
  90. if (textSize == -1) {
  91. textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, context.getResources().getDisplayMetrics());
  92. }
  93. if (spacing == -1) {
  94. spacing = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, context.getResources().getDisplayMetrics());
  95. }
  96. // 进行一些对象的初始化操作
  97. init();
  98. }
  99.  
  100. /**
  101. * 进行一些对象的初始化操作
  102. */
  103. private void init() {
  104. // 初始化绘制图标的画笔
  105. iconPaint = new Paint();
  106. iconPaint.setAntiAlias(true);
  107. iconPaint.setDither(true);
  108. // 初始化绘制文本的画笔
  109. textPaint = new Paint();
  110. textPaint.setAntiAlias(true);
  111. textPaint.setDither(true);
  112. textPaint.setColor(textColor);
  113. textPaint.setTextSize(textSize);
  114. // 初始化绘制元素的画笔
  115. tabPaint = new Paint();
  116. tabPaint.setAntiAlias(true);
  117. tabPaint.setDither(true);
  118. }
  119.  
  120. @Override
  121. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  122. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  123. width = getMeasuredWidth();
  124. height = getMeasuredHeight();
  125. }
  126.  
  127. @Override
  128. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  129. // 初始化绘制元素的画布
  130. tabBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  131. Canvas tabCanvas = new Canvas(tabBitmap);
  132. // 对图标进行缩放
  133. int widthLeft = width - getPaddingLeft() - getPaddingRight();
  134. int heightLeft = height - getPaddingTop() - getPaddingBottom() - textSize - spacing;
  135. float scale = (float) Math.min(widthLeft * 1.0 / iconWidth, heightLeft * 1.0 / iconHeight);
  136. Matrix matrix = new Matrix();
  137. matrix.postScale(scale, scale);
  138. iconBm = Bitmap.createBitmap(iconBm, 0, 0, (int) iconWidth, (int) iconHeight, matrix, true);
  139. iconWidth *= scale;
  140. iconHeight *= scale;
  141. // 将图标和文本绘制到绘制元素的画布上
  142. tabCanvas.drawBitmap(iconBm, (widthLeft - iconWidth) / 2, getPaddingTop(), iconPaint);
  143. int textWidth = (int) textPaint.measureText(text.toString());
  144. tabCanvas.drawText(text.toString(), (widthLeft - textWidth) / 2, height - getPaddingBottom(), textPaint);
  145. tmpBm = tabBitmap.copy(Bitmap.Config.ARGB_8888, false);
  146. tabPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
  147. tabPaint.setColor(backColor);
  148. tabCanvas.drawRect(0, 0, width, height, tabPaint);
  149. }
  150.  
  151. @Override
  152. protected void onDraw(Canvas canvas) {
  153. canvas.drawBitmap(tmpBm, 0, 0, null);
  154. // 绘制图标和文本
  155. Paint paint = new Paint();
  156. paint.setAlpha(alpha);
  157. canvas.drawBitmap(tabBitmap, 0, 0, paint);
  158. }
  159.  
  160. public void setAlpha(float alpha) {
  161. this.alpha = (int) Math.ceil(255 * alpha);
  162. invalidate();
  163. }
  164.  
  165. /**
  166. * 当这个View所在的Activity临时结束时,保存当前状态(透明度)
  167. */
  168. @Override
  169. protected Parcelable onSaveInstanceState() {
  170. Bundle bundle = new Bundle();
  171. bundle.putParcelable("default", super.onSaveInstanceState());
  172. bundle.putInt("alpha", alpha);
  173. return bundle;
  174. }
  175.  
  176. /**
  177. * 当这个View所在的Activity重新打开时,重置当前状态(透明度)
  178. */
  179. @Override
  180. protected void onRestoreInstanceState(Parcelable state) {
  181. if (state instanceof Bundle) {
  182. Bundle bundle = (Bundle) state;
  183. alpha = bundle.getInt("alpha");
  184. super.onRestoreInstanceState(bundle.getParcelable("default"));
  185. }
  186. super.onRestoreInstanceState(state);
  187. }
  188. }

  自定义属性文件 attr.xml 中的代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <attr name="backColor" format="color" />
  4. <attr name="iconImg" format="reference" />
  5. <attr name="spacing" format="dimension" />
  6. <attr name="text" format="string" />
  7. <attr name="textColor" format="color" />
  8. <attr name="textSize" format="dimension" />
  9.  
  10. <declare-styleable name="GradientTab">
  11. <attr name="backColor" />
  12. <attr name="iconImg" />
  13. <attr name="spacing" />
  14. <attr name="text" />
  15. <attr name="textColor" />
  16. <attr name="textSize" />
  17. </declare-styleable>
  18. </resources>

  主界面布局 activity_main.xml 文件中的代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent">
  6.  
  7. <LinearLayout
  8. android:id="@+id/gradienttab_main_ly_tabs"
  9. android:layout_width="match_parent"
  10. android:layout_height="60.0dip"
  11. android:layout_alignParentBottom="true"
  12. android:background="#EEEEEE"
  13. android:orientation="horizontal">
  14.  
  15. <my.itgungnir.custom_gradientmenu.GradientTab
  16. android:id="@+id/gradienttab_main_tab_tab1"
  17. android:layout_width="0.0dip"
  18. android:layout_height="match_parent"
  19. android:layout_weight="1"
  20. android:padding="3.0dip"
  21. app:backColor="#008800"
  22. app:iconImg="@mipmap/menu_tab1"
  23. app:spacing="2.0dip"
  24. app:text="Tab1"
  25. app:textColor="#888888"
  26. app:textSize="8.0sp" />
  27.  
  28. <my.itgungnir.custom_gradientmenu.GradientTab
  29. android:id="@+id/gradienttab_main_tab_tab2"
  30. android:layout_width="0.0dip"
  31. android:layout_height="match_parent"
  32. android:layout_weight="1"
  33. android:padding="3.0dip"
  34. app:backColor="#880000"
  35. app:iconImg="@mipmap/menu_tab2"
  36. app:spacing="2.0dip"
  37. app:text="Tab2"
  38. app:textColor="#888888"
  39. app:textSize="8.0sp" />
  40.  
  41. <my.itgungnir.custom_gradientmenu.GradientTab
  42. android:id="@+id/gradienttab_main_tab_tab3"
  43. android:layout_width="0.0dip"
  44. android:layout_height="match_parent"
  45. android:layout_weight="1"
  46. android:padding="3.0dip"
  47. app:backColor="#000088"
  48. app:iconImg="@mipmap/menu_tab3"
  49. app:spacing="2.0dip"
  50. app:text="Tab3"
  51. app:textColor="#888888"
  52. app:textSize="8.0sp" />
  53.  
  54. <my.itgungnir.custom_gradientmenu.GradientTab
  55. android:id="@+id/gradienttab_main_tab_tab4"
  56. android:layout_width="0.0dip"
  57. android:layout_height="match_parent"
  58. android:layout_weight="1"
  59. android:padding="3.0dip"
  60. app:backColor="#888888"
  61. app:iconImg="@mipmap/menu_tab4"
  62. app:spacing="2.0dip"
  63. app:text="Tab4"
  64. app:textColor="#888888"
  65. app:textSize="8.0sp" />
  66. </LinearLayout>
  67.  
  68. <android.support.v4.view.ViewPager
  69. android:id="@+id/gradienttab_main_vp_pages"
  70. android:layout_width="match_parent"
  71. android:layout_height="match_parent"
  72. android:layout_above="@id/gradienttab_main_ly_tabs" />
  73.  
  74. </RelativeLayout>

  主界面JAVA文件 MainActivity.java 中的代码:

  1. import android.os.Bundle;
  2. import android.support.v4.app.Fragment;
  3. import android.support.v4.app.FragmentActivity;
  4. import android.support.v4.app.FragmentPagerAdapter;
  5. import android.support.v4.view.ViewPager;
  6.  
  7. import java.util.ArrayList;
  8. import java.util.List;
  9.  
  10. public class MainActivity extends FragmentActivity {
  11. private ViewPager viewpager;
  12.  
  13. private List<GradientTab> tabList;
  14.  
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.activity_main);
  19. }
  20.  
  21. @Override
  22. protected void onResume() {
  23. super.onResume();
  24. viewpager = (ViewPager) findViewById(R.id.gradienttab_main_vp_pages);
  25. initData();
  26. initEvents();
  27. }
  28.  
  29. private void initData() {
  30. // 初始化盛放Fragment的列表
  31. final List<Fragment> list = new ArrayList<>();
  32. for (int i = 1; i <= 4; i++) {
  33. PageFragment fragment = new PageFragment();
  34. Bundle bundle = new Bundle();
  35. bundle.putString("title", "This is page " + i);
  36. fragment.setArguments(bundle);
  37. list.add(fragment);
  38. }
  39. // 为ViewPager适配数据
  40. viewpager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
  41. @Override
  42. public Fragment getItem(int position) {
  43. return list.get(position);
  44. }
  45.  
  46. @Override
  47. public int getCount() {
  48. return list.size();
  49. }
  50. });
  51. // 初始化四个Tab按钮,添加到List列表中
  52. tabList = new ArrayList<>();
  53. tabList.add((GradientTab) findViewById(R.id.gradienttab_main_tab_tab1));
  54. tabList.add((GradientTab) findViewById(R.id.gradienttab_main_tab_tab2));
  55. tabList.add((GradientTab) findViewById(R.id.gradienttab_main_tab_tab3));
  56. tabList.add((GradientTab) findViewById(R.id.gradienttab_main_tab_tab4));
  57. }
  58.  
  59. private void initEvents() {
  60. // ViewPager的滚动事件
  61. viewpager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
  62. @Override
  63. public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
  64. tabList.get(position).setAlpha(1 - positionOffset);
  65. tabList.get((int) Math.ceil(position + positionOffset)).setAlpha(positionOffset == 0 ? 1 : positionOffset);
  66. }
  67.  
  68. @Override
  69. public void onPageSelected(int position) {
  70. }
  71.  
  72. @Override
  73. public void onPageScrollStateChanged(int state) {
  74. }
  75. });
  76. }
  77. }

  运行效果图如下所示:

【Android - 自定义View】之自定义颜色渐变的Tab导航栏的更多相关文章

  1. Android 自定义View合集

    自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...

  2. Android 自定义 view(三)—— onDraw 方法理解

    前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自 ...

  3. Android 自定义 View 圆形进度条总结

    Android 自定义圆形进度条总结 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 微信公众号:牙锅子 源码:CircleProgress 文中如有纰漏,欢迎大家留言指出. 最近 ...

  4. Android 自定义 View 详解

    View 的绘制系列文章: Android View 绘制流程之 DecorView 与 ViewRootImpl Android View 的绘制流程之 Measure 过程详解 (一) Andro ...

  5. Android自定义View 画弧形,文字,并增加动画效果

    一个简单的Android自定义View的demo,画弧形,文字,开启一个多线程更新ui界面,在子线程更新ui是不允许的,但是View提供了方法,让我们来了解下吧. 1.封装一个抽象的View类   B ...

  6. (转)[原] Android 自定义View 密码框 例子

    遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...

  7. Android 自定义View (五)——实践

    前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...

  8. Android 自定义 view(四)—— onMeasure 方法理解

    前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...

  9. Android 自定义view(二) —— attr 使用

    前言: attr 在前一篇文章<Android 自定义view -- attr理解>已经简单的进行了介绍和创建,那么这篇文章就来一步步说说attr的简单使用吧 自定义view简单实现步骤 ...

随机推荐

  1. http和Https简介、详解

    目录 引用 一.HTTP和HTTPS的基本概念 二.HTTP与HTTPS有什么区别? 三.HTTPS的工作原理 四.HTTPS的优点 五.HTTPS的缺点 六.http切换到HTTPS 引用 超文本传 ...

  2. JVM内存结构、参数调优和内存泄露分析

    1. JVM内存区域和参数配置 1.1 JVM内存结构 Java堆(Heap) Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建.此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都 ...

  3. Bash 内置高效特性

    变量(字符串)变换 定义一个变量t,内容为framE [root@vm1 tmp]# t=framE 查看变量t的内容:echo $t或者是echo ${t} [root@vm1 tmp]# echo ...

  4. Web for pentester_writeup之XML attacks篇

    Web for pentester_writeup之XML attacks篇 XML attacks(XML攻击) Example 1 - XML外部实体注入(XXE) Payload http:// ...

  5. git 命令归纳版

    1.克隆: 单纯的克隆名字: git clone [url] 自定义新建项目名称: git clone [url] [项目名字]   2.跟踪文件: git add [文件名]   3.添加忽略文件 ...

  6. 使用float设置经典的网站前端结构(深入探讨)

    .要是DIV的子元素宽度大于它自己的宽度,不管子元素有没有脱离文档流,子元素会在横向向右溢出. 关于高度:1.要是DIV的高度没有设定,其高度受“没有脱离文档流”的子元素影响.以下是DIV宽度为0的情 ...

  7. 使用 Github + Hexo 从 0 搭建一个博客

    最近有几位同学在公众号后台留言问我的博客站是怎么建站的,思来想去,还是写一篇从 0 开始吧. 前置准备 我们先聊一下前置准备,可能很多同学一听说要自己搭一个博客系统,直接就望而却步.不得有台服务器么, ...

  8. 关于 Java 中多线程的面试问题 详解

    多线程细节: 1. 面试题: sleep 方法 和 wait 方法异同点是什么? 相同点: 可以让线程 处于 冻结状态. 不同点: 1. sleep 必须指定时间 wait 可以指定时间, 也可以不指 ...

  9. CSPS模拟 78

    大敛好稳啊..居然在模拟赛拿了540.. 有点畏惧.jpg 而我就是什么什么不行级人物了.. 真正在联赛拉开那么多分怎么追啊.. T1kmp?hash? T2 概率小到炸精时,对答案也就没贡献了 然后 ...

  10. 项目——基于httpd镜像演示Dockerfile所有的指令

    基于httpd镜像演示Dockerfile所有的指令: 第一步:创建Dockerfile工作目录 [root@localhost harbor]# mkdir /test [root@localhos ...