>>分享Android开发相关的技术 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 18942 个阅读者 刷新本主题
 * 贴子主题:  Android自定义组件 回复文章 点赞(0)  收藏  
作者:mary    发表时间:2020-05-15 06:28:50     消息  查看  搜索  好友  邮件  复制  引用

    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.        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 p_w_picpathParams =  new  LayoutParams(
  61.                       LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);  // 宽度WRAP_CONTENT,高度WRAP_CONTENT  
  62.               p_w_picpathParams.addRule(RelativeLayout.CENTER_VERTICAL,
  63.                       RelativeLayout.TRUE);  // 垂直居中  
  64.               p_w_picpathParams.leftMargin = bgLeftMargin;  // 左边距  
  65.               containerLayout.addView(mBgImage, p_w_picpathParams);  // 标题栏容器增加标题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  p_w_picpathOffset = pageOffset * itemDistance
  182.                               / mViewPager.getWidth();
  183.                        // 设置图像移动像素距离  
  184.                       moveImagePosition(p_w_picpathOffset);
  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  p_w_picpathOffset = pageOffset * itemDistance
  199.                               / mViewPager.getWidth();
  200.                        // 设置图像移动像素距离  
  201.                       moveImagePosition(p_w_picpathOffset);
  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.   }

----------------------------
原文链接:https://blog.51cto.com/vaero/872734

程序猿的技术大观园:www.javathinker.net



[这个贴子最后由 flybird 在 2020-06-05 08:15:29 重新编辑]
网站系统异常


系统异常信息
Request URL: http://www.javathinker.net/WEB-INF/lybbs/jsp/topic.jsp?postID=3294

java.lang.NullPointerException

如果你不知道错误发生的原因,请把上面完整的信息提交给本站管理人员