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

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

          嗯~,该怎么总结呢?

           一、概述

          自定义组件,大概可以这么分吧。一、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-04-08 08:53:59 重新编辑]
  Java面向对象编程-->接口
  JavaWeb开发-->在Web应用中访问Web服务
  JSP与Hibernate开发-->Spring、JPA与Hibernate的整合
  Java网络编程-->通过JDBC API访问数据库
  精通Spring-->虚拟DOM和render()函数
  Vue3开发-->CSS过渡和动画
  android webView js方法和java 方法交互-android,unity3d
  如何提高Android代码的安全性
  Android ListView高度问题
  通过Https访问的Not trusted Server Certificate的问题的解决...
  android cts 相关
  Android那些事儿之LBS定位
  android 自动化测试之MonkeyRunner学习
  Android带有粘性头部的ScrollView
  Android API 中Toast类的用法
  从Android中Activity之间的通信说开来
  Android语音识别 android.speech 包分析
  Android SDCard Mount 流程分析
  Android 活动(Activity)
  Android 之不要滥用 SharedPreferences(下)
  Android中使用Activity管理类
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。