现在的位置: 首页 > 综合 > 正文

Android自定义组件(一)

2018年04月02日 ⁄ 综合 ⁄ 共 10296字 ⁄ 字号 评论关闭

在原生组件上避免不了覆写、组合等,以定义自己的组件,也方便以后复用。例如之前工程里出现了多次的文件浏览器组件。

嗯~,该怎么总结呢?
一、概述
自定义组件,大概可以这么分吧。一、View或SurfaceView上自绘;二、ViewGroup布局子类整合;三、不清楚了~,好像也没什么好分的==。
本文的工程,个人觉着主要还是属性资源的使用吧?工程主要例子介绍如下:
名称
效果
属性
Loading动态...的效果组件
loading...的动态效果
定义了如下四属性:
1)loadImage:load字图片,reference类型
2)pointImage:小点图片,reference类型
3)pointCount:小点数目,integer类型
4)msecRate:毫秒级变化速率,integer类型
Title背景移位的效果组件
集合View布局,形成标题栏。实现了标题项下的背景移动的小效果。
定义了如下属性:
1)titleLayout:标题栏布局
2)bgImage:item背景图片
3)bgLeftMargin:背景初始左边距
4)animTime:移动动画时间
ViewPager绑定标题的效果组件
ViewPager绑定标题栏,并实现了标题项下的背景移动的小效果。
效果特征如下:
1)背景随ViewPager滚动而同步在标题间滚动
2)点击标题时,ViewPager程序控制滚动&背景同步
属性定义如下:
1)tLayout:标题栏布局
2)bImage:item背景图片
3)bMargin:背景初始左边距
ListView增加抽屉的效果组件
ListView增加抽屉的效果组件。抽屉打开的界面只用了一个。
1)listViewId:列表视图id,reference类型
2)drawerContent:抽屉内容视图id,reference类型
3)drawerClose:抽屉内容的关闭按钮id,reference类型
自定义能隐藏更多标题的组件
集合View布局,形成标题栏。实现超过标题数限制时,自动显示更多的效果。
初始化时,需要进行如下步骤:
1)设置显示数限制,默认将为6。
2)绑定标题内容。为String[],将直接以TextView显示==
3)绑定更多操作的视图id。将自己加载,并为其设置点击事件。
4)绑定更多显示的视图。应为已有的ViewGroup。将自动加载超出限制的标题内容(TextView)。更多操作则将控制其显示或隐藏。
另外,提供刷新内容的方法,用于:一、标题栏内容的重新加载;二,更多显示内容的重新加载。
自绘实时动态数据线
利用View绘制的实时数据显示组件?
写该文档时才挪进来的了,感觉弄得乱乱的。
双点缩放好像很不正确啊?应该是两个触摸点没弄对,获得的是一个手指头触发的两个点,所以一下放大了。(猜测,总之我是不修了^^)
以下将以“ViewPager扩展组件”为例了,顺便能看下ViewPager组件^^。
二、步骤
带属性资源,整合布局构建自定义组件的步骤~
1)attrs.xml
定义组件需要用的属性。不用的话,就相当于一个类为一个自定义组件,不和这些东西挂钩。“自定义能隐藏更多标题的组件”即是这样的,连属性都没定义==。
ViewPager扩展组件定义的内容:
  1. <!-- TitleViewPager -->
  2. <declare-styleable name="TitleViewPager">
  3. <attr format="reference" name="tLayout" />
  4. <attr format="reference" name="bImage" />
  5. <attr format="integer" name="bMargin" />
  6. </declare-styleable>
format类型,参见:Android中attr自定义属性详解
2)item.xml
只要是xml的resources标签内即可,单独弄出来呢最好。用以下方式定义一个id,用于View.setId(int id),主要用于相对布局时,相对于某个id的View什么的。
ViewPager扩展组件定义的内容:
  1. <item name="containerLayout" type="id"/>
3)创建组件
其类中引用属性资源。并看下ViewPager的使用说明吧:OnPageChangeListener接口方法和PagerAdapter适配器内方法的注释。
  1. public class TitleViewPager extends RelativeLayout implements
  2. OnPageChangeListener, View.OnClickListener {
  3. private Context mContext; // 上下文
  4. private LayoutInflater mInflater; // 布局加载器
  5. private int titleLayoutId; // 标题栏布局id
  6. private int bgImageResId; // item背景图片资源id
  7. private int bgLeftMargin; // 背景初始左边距
  8. private View titleLayout; // 标题栏布局
  9. private ImageView mBgImage; // item背景图片
  10. private ArrayList<View> mItemViews; // 标题项视图集合
  11. private ViewPager mViewPager; // ViewPager组件
  12. private ArrayList<View> mPageViews; // 页面视图集合
  13. private int prevOffset = -1; // 前次偏移值,这里用了int值像素
  14. private int currentIndex; // 当前页面索引
  15. private int previousIndex; // 前次页面索引
  16. private boolean isTitleClicked; // 标题项点击
  17. private OnPageChangeListener mOnPageChangeListener; // 页面变化监听事件
  18. // private final int REFRESH_RATE = 20; // 刷新速率20msec
  19. // private Scroller mScroller; // 滚动器
  20. // private static final Interpolator sInterpolator = new Interpolator() {
  21. // public float getInterpolation(float t) {
  22. // t -= 1.0f;
  23. // return t * t * t + 1.0f;
  24. // }
  25. // };
  26. public TitleViewPager(Context context, AttributeSet attrs) {
  27. super(context, attrs);
  28. mContext = context;
  29. mInflater = (LayoutInflater) context
  30. .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  31. // 获得TypedArray对象
  32. TypedArray typedArray = context.obtainStyledAttributes(attrs,
  33. R.styleable.TitleViewPager);
  34. // 获取标题栏布局id,默认0
  35. titleLayoutId = typedArray.getResourceId(
  36. R.styleable.TitleViewPager_tLayout, 0);
  37. // 获取item背景图片资源id,默认0
  38. bgImageResId = typedArray.getResourceId(
  39. R.styleable.TitleViewPager_bImage, 0);
  40. // 获取背景初始左边距,默认0
  41. bgLeftMargin = typedArray.getInt(R.styleable.TitleViewPager_bMargin, 0);
  42. initLayout(); // 初始化标题栏&ViewPager
  43. mItemViews = new ArrayList<View>();
  44. mPageViews = new ArrayList<View>();
  45. }
  46. // 初始化标题栏&ViewPager
  47. private void initLayout() {
  48. RelativeLayout containerLayout = new RelativeLayout(mContext); // 创建标题栏容器布局
  49. containerLayout.setId(R.id.containerLayout); // 设置标识符
  50. LayoutParams containerParams = new LayoutParams(
  51. LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); // 宽度WRAP_CONTENT,高度WRAP_CONTENT
  52. containerParams.addRule(RelativeLayout.ALIGN_PARENT_TOP,
  53. RelativeLayout.TRUE); // 贴于顶部
  54. containerParams.addRule(RelativeLayout.CENTER_HORIZONTAL,
  55. RelativeLayout.TRUE); // 水平居中
  56. addView(containerLayout, containerParams); // 当前布局增加容器布局
  57. if (0 != bgImageResId) {
  58. mBgImage = new ImageView(mContext); // 创建item背景图片
  59. mBgImage.setImageResource(bgImageResId); // 设置item背景图片
  60. LayoutParams imageParams = new LayoutParams(
  61. LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); // 宽度WRAP_CONTENT,高度WRAP_CONTENT
  62. imageParams.addRule(RelativeLayout.CENTER_VERTICAL,
  63. RelativeLayout.TRUE); // 垂直居中
  64. imageParams.leftMargin = bgLeftMargin; // 左边距
  65. containerLayout.addView(mBgImage, imageParams); // 标题栏容器增加标题item背景图片
  66. }
  67. if (titleLayoutId != 0) {
  68. titleLayout = mInflater.inflate(titleLayoutId, this, false); // 获得标题栏布局
  69. LayoutParams titleParams = new LayoutParams(
  70. LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); // 宽度WRAP_CONTENT,高度WRAP_CONTENT
  71. titleParams.addRule(RelativeLayout.CENTER_HORIZONTAL,
  72. RelativeLayout.TRUE); // 水平居中
  73. titleParams.addRule(RelativeLayout.CENTER_VERTICAL,
  74. RelativeLayout.TRUE); // 垂直居中
  75. containerLayout.addView(titleLayout, titleParams); // 标题栏容器增加标题栏布局
  76. }
  77. mViewPager = new ViewPager(mContext); // 创建ViewPager
  78. mViewPager.setAdapter(new MPagerAdapter()); // 设置ViewPager适配器
  79. mViewPager.setOnPageChangeListener(this); // 设置页面改变的监听接口
  80. LayoutParams viewPagerParams = new LayoutParams(
  81. LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); // 宽度FILL_PARENT,高度FILL_PARENT
  82. viewPagerParams.addRule(RelativeLayout.BELOW, containerLayout.getId()); // 布于标题栏容器下方
  83. viewPagerParams.addRule(RelativeLayout.CENTER_HORIZONTAL,
  84. RelativeLayout.TRUE); // 水平居中
  85. addView(mViewPager, viewPagerParams); // 当前布局增加容器ViewPager
  86. }
  87. // 增加一个绑定页面
  88. public void addBindedPage(int pageViewId, int titleItemId) {
  89. mPageViews.add(mInflater.inflate(pageViewId, this, false));
  90. View item = titleLayout.findViewById(titleItemId);
  91. item.setOnClickListener(this);
  92. mItemViews.add(item);
  93. }
  94. // 获得页面数量
  95. public int getCount() {
  96. return mPageViews.size();
  97. }
  98. // 初始化页面(需要在UI加载完后,可以覆写onWindowFocusChanged())
  99. public void setPage(int index) {
  100. setImagePosition(index); // 设置图像至标题项位置
  101. mViewPager.setCurrentItem(index, false); // 设置ViewPager当前页面
  102. }
  103. // 设置当前页面
  104. public void setCurrentPage(int index, boolean isAnim) {
  105. previousIndex = currentIndex; // 记录前次页面索引
  106. currentIndex = index; // 设置当前页面索引
  107. mViewPager.setCurrentItem(index, isAnim); // 设置ViewPager当前页面
  108. // Title移动绑定在ViewPager的滚动事件内
  109. }
  110. // 设置图像至标题项位置
  111. private void setImagePosition(int index) {
  112. previousIndex = currentIndex; // 记录前次页面索引
  113. currentIndex = index; // 设置当前页面索引
  114. if (null == mBgImage) {
  115. return;
  116. }
  117. LayoutParams params = (RelativeLayout.LayoutParams) mBgImage
  118. .getLayoutParams(); // 获得图片布局
  119. View item = mItemViews.get(index); // 标题项
  120. // 注:UI加载完后getLeft()才有值
  121. int targetLeftMargin = (int) (item.getLeft() + item.getWidth() / 2.0 - mBgImage
  122. .getWidth() / 2.0); // 目标左边距
  123. // 位置未变时直接返回,以避免多次setLayoutParams(...)
  124. if (params.leftMargin == targetLeftMargin) {
  125. return;
  126. }
  127. params.leftMargin = targetLeftMargin;
  128. mBgImage.setLayoutParams(params); // 使设置生效
  129. }
  130. // 设置图像移动像素距离
  131. private void moveImagePosition(int offset) {
  132. if (null == mBgImage) {
  133. return;
  134. }
  135. LayoutParams params = (RelativeLayout.LayoutParams) mBgImage
  136. .getLayoutParams(); // 获得图片布局
  137. params.leftMargin += offset;
  138. mBgImage.setLayoutParams(params); // 使设置生效
  139. }
  140. /*
  141. * 当前页滚动时调用,无论是程序控制的平滑滚动还是用户发起的触摸滚动。
  142. * arg0:第一个页面当前显示的位置索引。如果页面偏移不是0,下一个页面将会可见。
  143. * arg1:表示第二个页面位置偏移量的比例值,[0, 1)。(右侧页面所占屏幕百分比)
  144. * arg2:表示第二个页面位置偏移量的像素值。(右侧页面距右边的像素值)
  145. */
  146. @Override
  147. public void onPageScrolled(int position, float positionOffset,
  148. int positionOffsetPixels) {
  149. // 判断是否不在动画,用positionOffsetPixels判断判断原因是:
  150. // 1)position,在选中了页面时就会改变,自动动画时的不能判断
  151. // 2)positionOffset,动画完变为0.0,但float不好直接等于判断
  152. // 3)positionOffsetPixels,动画完变为0,int型^^
  153. if (positionOffsetPixels == 0) {
  154. setImagePosition(position);
  155. prevOffset = -1;
  156. isTitleClicked = false;
  157. return;
  158. }
  159. // 刚移动时,记录下该次值
  160. if (prevOffset == -1) {
  161. prevOffset = positionOffsetPixels;
  162. return;
  163. }
  164. int pageOffset = positionOffsetPixels - prevOffset; // 页面偏移距离
  165. prevOffset = positionOffsetPixels;
  166. if (null != mBgImage) {
  167. try {
  168. if (pageOffset < 0) { // 左->右
  169. int prevIndex, nextIndex;
  170. if (isTitleClicked) {
  171. prevIndex = previousIndex;
  172. nextIndex = currentIndex;
  173. } else {
  174. prevIndex = currentIndex;
  175. nextIndex = currentIndex - 1;
  176. }
  177. // 两菜单项间的距离
  178. int itemDistance = mItemViews.get(prevIndex).getLeft()
  179. - mItemViews.get(nextIndex).getLeft();
  180. // 图片偏移距离
  181. int imageOffset = pageOffset * itemDistance
  182. / mViewPager.getWidth();
  183. // 设置图像移动像素距离
  184. moveImagePosition(imageOffset);
  185. } else if (pageOffset > 0) { // 右->左
  186. int prevIndex, nextIndex;
  187. if (isTitleClicked) {
  188. prevIndex = previousIndex;
  189. nextIndex = currentIndex;
  190. } else {
  191. prevIndex = currentIndex;
  192. nextIndex = currentIndex + 1;
  193. }
  194. // 两菜单项间的距离
  195. int itemDistance = mItemViews.get(nextIndex).getLeft()
  196. - mItemViews.get(prevIndex).getLeft();
  197. // 图片偏移距离
  198. int imageOffset = pageOffset * itemDistance
  199. / mViewPager.getWidth();
  200. // 设置图像移动像素距离
  201. moveImagePosition(imageOffset);
  202. }
  203. } catch (IndexOutOfBoundsException e) {
  204. // 类似在中间左右左右的来回拖拽==,判断还不够啊T^T
  205. setImagePosition(currentIndex);
  206. isTitleClicked = false;
  207. }
  208. }
  209. if (null != mOnPageChangeListener) {
  210. mOnPageChangeListener.onPageScrolled(position, positionOffset,
  211. positionOffsetPixels);
  212. }
  213. }
  214. /*
  215. * 当一个新页面被选中时被调用。动画不一定必须完成。
  216. * arg0:新选中页面的位置索引
  217. */
  218. @Override
  219. public void onPageSelected(int position) {
  220. if (null != mOnPageChangeListener) {
  221. mOnPageChangeListener.onPageSelected(position);
  222. }
  223. }
  224. /*
  225. * 滚动状态改变时调用。用于发现用户何时开始拖动、页面何时自动沉降到当前页(用户不拖动时)、或者何时完全停止/空闲。
  226. * arg0:新的滚动状态。SCROLL_STATE_DRAGGING、SCROLL_STATE_SETTLING、SCROLL_STATE_IDLE
  227. */
  228. @Override
  229. public void onPageScrollStateChanged(int state) {
  230. if (state == ViewPager.SCROLL_STATE_DRAGGING) { // 用户拖动时
  231. isTitleClicked = false;
  232. }
  233. if (null != mOnPageChangeListener) {
  234. mOnPageChangeListener.onPageScrollStateChanged(state);
  235. }
  236. }
  237. // 自定义的ViewPager适配器
  238. private class MPagerAdapter extends PagerAdapter {
  239. /*
  240. * 移除一个给定位置的页面。适配器有责任从它的容器中移除视图,虽然这仅必须确认动作是在finishUpdate()后按时间完成的。
  241. * arg0:容器视图,从中将移除页面。
  242. * arg1:移除的页面位置
  243. * arg2:和instantiateItem(View, int)返回的一样的对象
  244. */
  245. @Override
  246. public void destroyItem(View arg0, int arg1, Object arg2) {
  247. ((ViewPager) arg0).removeView(mPageViews.get(arg1));
  248. }
  249. /*
  250. * 当显示的界面完成变化后调用。在这里,你应当必须确保所有的页面已经真正的从容器中增加或删除。
  251. * arg0:容器视图,用于显示适配器的页面视图
  252. */
  253. @Override
  254. public void finishUpdate(View arg0) {
  255. }
  256. // 返回可用界面的数量
  257. @Override
  258. public int getCount() {
  259. return mPageViews.size();
  260. }
  261. /*
  262. * 创建一个给定位置的界面。适配器有责任给这边给出的容器增加一个视图,虽然这仅必须确认动作是在finishUpdate()后按时间完成的。
  263. * arg0:容器视图,在里面将显示页面。
  264. * arg1:要被装载的页面位置
  265. * Object:返回一个展现新画面的对象。这不必须是一个View,也可以是一些其他的页面容器。
  266. */
  267. @Override
  268. public Object instantiateItem(View arg0, int arg1) {
  269. ((ViewPager) arg0).addView(mPageViews.get(arg1), 0);
  270. return mPageViews.get(arg1);
  271. }
  272. // 是否是由对象生成的视图
  273. @Override
  274. public boolean isViewFromObject(View arg0, Object arg1) {
  275. return arg0 == (arg1);
  276. }
  277. // 恢复状态
  278. @Override
  279. public void restoreState(Parcelable arg0, ClassLoader arg1) {
  280. }
  281. // 保存状态,返回序列化对象
  282. @Override
  283. public Parcelable saveState() {
  284. return null;
  285. }
  286. /*
  287. * 当显示的页面将要开始变化时调用
  288. * arg0:容器视图,用于显示适配器的页面视图
  289. */
  290. @Override
  291. public void startUpdate(View arg0) {
  292. }
  293. }
  294. @Override
  295. public void onClick(View v) {
  296. int size = mItemViews.size(); // 大小
  297. for (int i = 0; i < size; i++) {
  298. if (mItemViews.get(i).getId() == v.getId()) {
  299. isTitleClicked = true;
  300. setCurrentPage(i, true);
  301. break;
  302. }
  303. }
  304. }
  305. // 获得标题项视图集合
  306. public ArrayList<View> getItemViews() {
  307. return mItemViews;
  308. }
  309. // 获得 页面视图集合
  310. public ArrayList<View> getPageViews() {
  311. return mPageViews;
  312. }
  313. // 设置页面变化监听事件
  314. public void setOnPageChangeListener(OnPageChangeListener listener) {
  315. mOnPageChangeListener = listener;
  316. }
  317. }

抱歉!评论已关闭.