首页 > 代码库 > 【android基础篇】自定义控件实现一些比较常见的功能

【android基础篇】自定义控件实现一些比较常见的功能

I,我要实现的效果

如图所示:

当下拉时,显示下拉刷新。松开后,显示松开刷新,接着正在刷新。

II,准备工作

1)需要自定义ListView,写一个类继承ListView。并在ListView中添加一些数据。

 1 private void initView() { 2     listData = http://www.mamicode.com/new ArrayList<String>(); 3         for (int i = 0; i <30; i++) { 4             listData.add("我是来自仙桃的西藏哥,我真的很爱骑行......"+i); 5         } 6         ManualListView manualView= (ManualListView) findViewById(R.id.manual_view); 7         manualView.setAdapter(new MyAdapter()); 8     } 9     10     private class MyAdapter extends BaseAdapter{11         @Override12         public int getCount() {13             return listData.size();14         }15         @Override16         public Object getItem(int position) {17             18             return null;19         }20         @Override21         public long getItemId(int position) {22             return 0;23         }24         @Override25         public View getView(int position, View convertView, ViewGroup parent) {26             TextView tv_word=new TextView(MainActivity.this);27             tv_word.setTextSize(20);28             tv_word.setText(listData.get(position));29             return tv_word;30         }31     }

其实ListView中有两个方法值得关注:

manualView.addHeaderView(view)和manualView.addFooterView(view),这两个方法可以往ListView中去添加组件。

所以我们可以参照头部组件的样式写一个布局文件,并添加到ListView的头部。

下面是自定义ListView下初始化头部组件的方法:

1    /**2      * 往ListView的头部添加组件。去写一个布局文件3      */4     public void initHeadView(){5         View mHeadView=View.inflate(getContext(), R.layout.activity_head, null);6         addHeaderView(mHeadView);7     }

自定义的ListView中,实现父类的三个构造方法,并调用initHeadView方法。

这时候可以运行,可以看出,头部布局已经显现出来。

我们可以看出,头部组件中的progressBar的样式并不是我们所需要的样子,所以这里还需要自定义一下环形的progressBar。

 

II,自定义ProgressBar的样式

创建一个资源类型为drawable的文件,并且它的根节点为:shape

其中有两个属性:innerRadiusRatio表示的是内半径比,thicknessRation表示的是厚度比。

 1 <?xml version="1.0" encoding="utf-8"?> 2 <shape xmlns:android="http://schemas.android.com/apk/res/android" 3     android:shape="ring" 4     android:innerRadiusRatio="2.5" 5     android:thicknessRatio="10"> 6      7     <gradient  8         android:startColor="#ffffff" 9         android:centerColor="#ff6666"10         android:endColor="#ff0000"
      android:type="sweep"11 />12 </shape>

gradient表示的颜色的变更,开始的颜色,中间的颜色,最后的颜色。type表示的是颜色渐变的程度。

由于ProgressBar需要实现旋转效果,所以还要有Rotate节点,最后写为:

 1 <?xml version="1.0" encoding="utf-8"?> 2 <rotate xmlns:android="http://schemas.android.com/apk/res/android" 3     android:fromDegrees="0" 4     android:toDegrees="360" 5     android:pivotX="50%" 6     android:pivotY="50%"> 7         <shape xmlns:android="http://schemas.android.com/apk/res/android" 8         android:shape="ring" 9         android:innerRadiusRatio="2.5"10         android:thicknessRatio="10">11         <gradient 12             android:startColor="#ffffff"13             android:centerColor="#ff6666"14             android:endColor="#ff0000"15             android:type="sweep"16             />17         </shape>18 </rotate>

其中fromDegress和toDegress分别表示是,旋转开始的结束的角度。pivotX和pivotY则表示旋转中心的位置。写为50%,表示位于圆心。

写好了布局文件后,就可以在ProgressBar中去关联。

android:indeterminateDrawable="@drawable/manual_progress"
indeterminate表示不确定之意。

运行后的结果:

 

progressBar的确实现了旋转,颜色变化的效果。

接下来要做的就是拉动ListView,就应该把头部组件隐藏掉。隐藏头部组件就不得不说,PaddingTop这个值。它表示的是和顶部的内边距。

当PaddingTop为正数的时候,那么头部组件的高度也就越来越大,为负数,则往上面移动,也就是慢慢的消失。

当为负的PrgressBar时,即头部组件刚刚消失在屏幕上。所以有:

1 public void initHeadView(){2         mHeadView = View.inflate(getContext(), R.layout.activity_head, null);3         mHeadView.measure(0, 0);4         int mHeadHeight=mHeadView.getMeasuredHeight();5         mHeadView.setPadding(0, -mHeadHeight, 0, 0);6         addHeaderView(mHeadView);7     }

mHeadView.measure(widthMeasureSpec, heightMeasureSpec)为什么设置为mHeadView.measure(0,0);

因为widthMeasureSpec和heightMeasureSpec表示的测量的规格,如果不给它规则,不指定它的宽和高,也就设置为0。

measureHeight = mHeadView.getMeasuredHeight();也就是拿到测量后的高度。

 

写OnTouchEvent事件:

首先判断,头部组件什么时候应该出现,什么时候应该隐藏。

 1 public boolean onTouchEvent(MotionEvent ev) { 2         switch (ev.getAction()) { 3         case MotionEvent.ACTION_DOWN: 4              5             break; 6         case MotionEvent.ACTION_MOVE: 7              8             break; 9         case MotionEvent.ACTION_UP:10 11             break;12         default:13             break;14         }15         return super.onTouchEvent(ev);16     }

MotionEvent.ACTION_DOWN表示手指按下时,MotionEvent.ACTION_MOVE表示手指移动时。我们可以分别的获取它们在Y轴上,也就是关于高度的值。

这时会有这几种情况,当MoveY-DownY为负值时,表示向上移动(因为安卓中,以屏幕的左上角为原点)。为正值时,表示向下移动。所以可以判断只有在下拉时,才显示

头部组件。但是还有一些情况,也要考虑,比如早LIstView的中间,无论上拉下拉都是ListView的条目时,是没有必要显示头部进度条的。所以这里可以判断,除了前面的条件外,

还得满足一个条件,即要是当前的ListView最前面的item的索引为0时,才下拉。此外,还有就是下拉的高度是什么?隐藏的头部组件的高度的负值加上移动的MoveY-DownY值就是下拉时移动的高度。于是就有:

 1 public boolean onTouchEvent(MotionEvent ev) { 2         switch (ev.getAction()) { 3         case MotionEvent.ACTION_DOWN: 4             downY = (int) ev.getY(); 5             break; 6         case MotionEvent.ACTION_MOVE: 7             moveY = (int) ev.getY(); 8             int diffY=moveY-downY; 9             if(diffY>0&&getFirstVisiblePosition()==0){10                 int paddingTop=-mHeadHeight+diffY;11                 mHeadView.setPadding(0, paddingTop, 0, 0);12                 return true;13             }14             break;15         case MotionEvent.ACTION_UP:16             break;17         default:18             break;19         }20         return super.onTouchEvent(ev);21     }

return true的意思是表示不让父类的方法响应触摸事件。

 接下类来思考的问题是:什么时候是下拉刷新,什么时候是释放数显,什么时候是正在刷新?

我们可以根据paddingTop值来判断,当paddingTop小于0表示头部组件还未全部进入屏幕时,显示下拉刷新,大于0表示已经进入屏幕,显示释放刷新。

当时这里依然会存在一些问题,比如当你已经进入释放刷新的状态,还在刷新时,会不断的执行paddingTop大于0的语句。所以这里需要写些状态来区分,达到只执行一次的结果。代码:

 1     public boolean onTouchEvent(MotionEvent event) { 2         switch (event.getAction()) { 3         case MotionEvent.ACTION_MOVE: 4             moveY = (int) event.getY(); 5             /** 6              * 计算出移动的间距。向上滑动时,应该显示原生的。 7              * moveY-downY如果计算的是负值,那么就是向上移动的。还有就是在ListView中间拉动时,也不应该拉出头布局。 8              * 这两种情况都应该相应ListView的原生的状态。 9              * 10              * 如果是正数,那就是向下移动。并且ListView顶部的条目索引为0时。11              */12             int diffY=moveY-downY;13             if(diffY>0&&getFirstVisiblePosition()==0){14                 /**15                  * 执行下拉操作16                  */17                 int paddingTop=-measureHeight+diffY;18                 /**19                  *因为我不断的下拉的话,会不断的进入到某个状态,所以这里设置几个值来代表区分状态。20                  */21                 if(paddingTop>0&&currentState!=RELEASE_REFRESH){22                     //进入释放刷新状态23                     System.out.println("进入释放刷新状态");24                     currentState=RELEASE_REFRESH;25                 }else if(paddingTop<0&&currentState!=PULL_DOWN){26                     //进入到下拉刷新状态27                     System.out.println("进入到下拉刷新状态");28                     currentState=PULL_DOWN;29                 }30                 mHeadView.setPadding(0, paddingTop, 0, 0);31                 //不让父类的ListView响应事件,自己来处理......32                 return true;33             }34             break;35         case MotionEvent.ACTION_UP:36             break;37         case MotionEvent.ACTION_DOWN:38             downY = (int) event.getY();39             break;40         default:41             break;42         }43         /**44          * super.onTouchEvent(event);表示的是原生的滑动事件45          */46         return super.onTouchEvent(event);47     }

PULL_DOWN表示的是下拉刷新状态,而RELEASE_REFRESH表示释放刷新状态。

现在我们就可以根据状态来确定头部组件显示的文字。

 

【android基础篇】自定义控件实现一些比较常见的功能