【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&¤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表示释放刷新状态。
现在我们就可以根据状态来确定头部组件显示的文字。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。