首页 > 代码库 > 【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&¤tState!=RELEASE_REFRESH){22 //进入释放刷新状态23 System.out.println("进入释放刷新状态");24 currentState=RELEASE_REFRESH;25 }else if(paddingTop<0&¤tState!=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基础篇】自定义控件实现一些比较常见的功能