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

I,我要实现的效果

如图所示:

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

II,准备工作

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

 1 private void initView() {
 2     listData = 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         @Override
12         public int getCount() {
13             return listData.size();
14         }
15         @Override
16         public Object getItem(int position) {
17             
18             return null;
19         }
20         @Override
21         public long getItemId(int position) {
22             return 0;
23         }
24         @Override
25         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表示释放刷新状态。

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

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。