Android自定义组件(一)

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