二.自定义View之ListView下拉刷新,添加头脚布局,观察者模式

本章属于第三种自定义控件,继承已有控件,扩展其功能。


注意:

1.ListView的addHeaderView(view)/addFooterView(view)需要在ListView的setAdapter之前执行。

2.在onTouchEvent中,如果返回值为true,说明当前事件被消费,返回值false,说明不消费该事件。



步骤:

1.自定义RefreshListView继承ListView,重写其构造方法。在构造方法中initView()

2.给ListView添加头布局,并对头布局做相应的处理,根据下拉的状态不同而更改头布局的UI

1)使用:addHeaderView(view);

2)根据更改HeaderView的paddingTop为-height隐藏头布局。

mHeaderView = View.inflate(getContext(), R.layout.layout_header_view,null);

mHeaderView.measure(0,0);//传入0,表示按照Xml中设置的宽高进行测量

mHeaderViewHeight =mHeaderView.getMeasuredHeight();

mHeaderView.setPadding(0,-mHeaderViewHeight ,0,0);

addHeaderView(mHeaderView);


获取头布局的高度需要注意:

此时是无法获取到头布局的高度的,因为一进入页面在oncreate方法中findviewById找到控件,此时,自定义控件的构造函数就已经调用,initView方法即调用,而自定义控件的渲染是在onCreate()方法之后。此时使用mHeaderView.getMeasuredHeight()或者mHeaderView.getHeight()方法拿到的高度值为0.

解决方法:在获取高度之前手动测量一下控件的宽高。

mHeaderView.measure(0,0);//传入0,表示按照Xml中设置的宽高进行测量,(父View已经测量过子View之后,填0是指按照XML中设置的宽高进行测量,一般是传childView.getLayoutParams.width来进行测量)

测量之后,使用mHeaderView.getMeasuredHeight()可以取得高度值。(getHeight是获得mHeaderView真实显示在界面上的高度)


3)下拉时,通过不停的更改-paddingTop值来使头布局慢慢显示出来。

a.监听ListView的触摸事件。重写onToucheEvent(),(不能删除return的super.onTouchEvent(ev),源码中ListView做了很多的处理,如果删除,则ListView无法滑动。)判断滑动距离来判断头布局的偏移量。


分析

b.两种情况不会显示头布局,第一种是disY<0说明屏幕在向上滑动,第二种,第一条可见的条目position不为0,此时不需要显示头布局,那么就不需要设置padding值。

case MotionEvent.ACTION_DOWN:

//按下时获取按下Y坐标

    downY = ev.getY();

break;

case MotionEvent.ACTION_MOVE:

moveY = ev.getY();

    disY =moveY -downY;

    if(disY >0 && getFirstVisiblePosition() ==0){

mHeaderView.setPadding(0,(int)(-mHeaderViewHeight+disY),0,0);

    }

break;

c.下拉结束后(即头布局完全显示),需要将箭头更改为向上,并且更改文字下拉刷新为松开刷新。当paddingTop >= 0时,更新UI。

定义几个int常量,记录头布局的状态:if paddingTop > = 0,说明头布局完全显示,那么此时头布局的状态应该为松开刷新,状态记为1:REFLEASE_REFRESH 

如果paddingTop<0,说明头布局未全部显示,此时头布局为下拉刷新,状态记为0.还有一种状态是正在刷新,状态记为0:PULL_TO_REFRESH

正在刷新:REFRESHING = 2;

代码方面,为了避免时时检测paddingTop(因为手指在屏幕上每一次微小的移动都会调用ACTION_MOVE这个状态下的代码,添加判断,只会在状态改变时进入执行if中更改动画、文字等代码),节约性能,可以把更改文字和动画的动作之前添加一个判断

if(paddingTop>=0&&currentState != REFLEASE_REFRESH){

.....//此处为更改动画和文字的代码:松开刷新状态,当前状态不为松开刷新状态时,才会进入这里

}else if(paddingTop < 0 && currentSate != PULL_TO_REFRESH){

//同上,当前状态不为下来刷新状态时,才会进来这里,如果状态已经是下来刷新状态,即使paddingTop<0,也不会进来这里。}

此处应该注意的是,如果在MOVE中添加了自己的事件,在MOVE中break前,添加return true;表示当前事件被我们处理并消费。

d.手指松开时的监听处理:

1.当手指松开时,头布局未完全显示,即paddingTop<0,还没有转化成松开刷新状态,即当前状态为下拉刷新PULL_TO_REFRESH,此时松开ListView应该是弹回去,即头布局隐藏,把头布局的paddingTop设置为-measureHeight即可。

2.当手机松开时,头布局已经完全显示,即paddingTop>=0,已经转化成松开刷新状态,即当前状态是REALEASE_REFRESH,此时松开,头布局paddingTop设置为0,并且上面的文字改变为正在刷新,iv隐藏(隐藏之前要清除动画,否则无法隐藏),pb显示。

3.如果状态为正在刷新中,控制用户不能拖拽,在ACTION_MOVE事件中,添加判断,如果是正在刷新,则执行父类对touch事件的处理。

if(current == REFRESHING){

//正在刷新时,不能往下拖拽,执行父类的touch事件的处理方式,当头布局显示完全时,不能拖拽

return super.onTouchEvent(ev);

}

效果图:

下拉刷新效果图



3.监听回调,当头布局状态为正在刷新时,需要告知外界,这时我正在下拉刷新,外界需要调用相关方法进行下拉刷新。即观察者模式。

a.在RefreshListView中定义一个接口OnRefreshListener,接口中添加方法onRefresh();

public interface OnRefreshListener{

             void onRefresh();

}

b.在RefreshListView中添加方法setOnRefreshListener(OnRefreshListener listener),便于外界使用该接口,

public void setOnRefreshListener(OnRefreshListener listener){

this.listener = listener;

}


外界使用方法:此处在MAinActivity中

refreshListView.setOnRefreshListener(new OnRreshListener(){

onRefresh(){

//当RefreshListView的状态为正在刷新时,这个地方的方法会被调用

}});


c.在自定义View中,在适当的位置调用onRefresh()方法,比如在这个案例中,当用户手指抬起并且状态为正在刷新时,调用该方法,在自定义View中调用onRefresh(),实际上外界的(这里是MainActivity中的onRefresh被调用),此时可以把自定义View中的某些数据作为参数传递到界面上。


d.下拉刷新:一般情况 ,下拉都是重新加载一遍数据。在这里模拟加载一条数据,首先添加到list中,再通知adapter更新数据即可。刷新完成之后需要通知RefreshListView,把头布局收起来,因此在RefreshView中定义一个方法,completedRefresh(),在方法中更改当前状态,隐藏头布局,更新UI。


执行下拉刷新


刷新完成之后更改布局

4.添加脚布局,上拉加载更多。

a.添加脚布局并隐藏

mFooterView = View.inflate(getContext(), R.layout.layout_foot_view, null);

mFooterView.measure(0,0);

mFooterViewHeight =mFooterView.getMeasuredHeight();

mFooterView.setPadding(0,-mFooterViewHeight,0,0);

addFooterView(mFooterView);

b.添加onScrollListener,判断滑动状态,如果滑动状态为空闲状态,并且滑动到最后一个条目时,显示脚布局,跳到脚布局。

onScrollListener中的两个方法:onScrollStateChanged,当滑动状态改变时调用,滑动状态有三种分别是:

1.SCROLL_STATE_IDLE = 0 空闲状态,源码中的解释为:

The view is not scrolling. Note navigating the list using the trackball counts as being in the idle state since these transitions are not animated.

2.SCROLL_STATE_TOUCH_SCROLL = 1 用户在进行触摸滚动,源码中的解释为:

The user is scrolling using touch, and their finger is still on the screen

3.SCROLL_STATE_FLING = 2 滑翔状态 源码中的解释为:

The user had previously been scrolling using touch and had performed a fling. The animation is now coasting to a stop

用户滚动内容时,滚动状态变化的顺序为:0 --> 1--> 2 --> 0,即空闲-->用户开始滑动屏幕-->用户手指离开屏幕但屏幕仍在滑动-->滑翔结束回到空闲状态

此时需要在滚动状态重新回到空闲时判断是否滚动到最后一条,滚动到最后一条即显示脚布局:但是要注意,如果此时已经正在加载,用户往上拉的时候仍然会执行这几行代码,再一次进行加载更多的操作,为了避免这种情况发生,可以添加一个boolean类型的变量,进行标记和判断。

@Override

public void onScrollStateChanged(AbsListView view, int scrollState) {

//当滚动状态改变时调用,当用户滑到最后一个并且滚动状态为空闲,getCount()得到的是adapter中的list中数据的总条数

       if(scrollState == SCROLL_STATE_IDLE && getLastVisiblePosition() == getCount() - 1&&!isLoadingMore){

               isLoadingMore= true;//标记为true,说明正在加载。

              //说明滚到最后一条,显示脚布局

               mFooterView.setPadding(0,0,0,0);

               setSelection(getCount());//显示最后一条

}

c.接口回调

1.在OnRefreshListener中添加方法,onLoadMore(),用于加载更多数据。


添加onLoadMore方法

2.在RefreshListView中的脚布局出现时调用onLoadMore(),与下拉刷新相同,实际上onLoadMore()方法是在界面中使用接口时被调用。

在界面中进行加载更多的处理。


3.加载完成同样调用completedRefresh()方法,在方法中处理。判断是下拉刷新还是上拉加载更多。



完成效果图



扩展内容:

1.自定义ProgressBar:

xml中添加该属性:indeterminateDrawable 无限循环的drawable

该属性的值为shape。


<rotate xmlns:android="http://schemas.android.com/apk/res/android"

android:fromDegrees="0"

    android:toDegrees="360"

    android:pivotX="50%"

    android:pivotY="50%"

    >

<!--旋转动画中可以包含shapeandroid:pivotX="50%" android:pivotY="50%" 相对于自己的中心位置android:pivotX="50%p" android:pivotY="50%p" 相对于父控件的中心位置-->

<shape

        android:shape="ring"

        android:innerRadius="@dimen/dp_20"

        android:thickness="@dimen/dp_5"

        android:useLevel="true"

        android:innerRadiusRatio="2.5"

        android:thicknessRatio="10"

      >

<gradient android:startColor="#88E93751"

            android:centerColor="#33E93751"

            android:endColor="#00000000"

            android:type="sweep"/>

   <!--在颜f色值前面添加00-f 00纯透明,ff不透明

         sweep:扫描-->

<!--

          内半径innerRadius

          厚度thickness

          内圆半径比 innerRadiusRatio="2.5" 内圆半径与容器宽高比

          圆环厚度比 thicknessRatio="10" 圆环厚度与容器宽高比

          -->

</shape>

</rotate>

2.旋转动画

头布局中箭头的旋转动画

//向下翻转动画

public static void RotateDown(View view){

RotateAnimation animation = new RotateAnimation(

180f,0f,

Animation.RELATIVE_TO_SELF, 0.5f,

Animation.RELATIVE_TO_SELF, 0.5f);

animation.setDuration(500);

animation.setFillAfter(true);

view.startAnimation(animation);

}

//向上翻转动画

public static void RotateUp(View view){

RotateAnimation animation = new RotateAnimation(

0f, 180f,

Animation.RELATIVE_TO_SELF, 0.5f,

Animation.RELATIVE_TO_SELF, 0.5f);

animation.setDuration(500);

animation.setFillAfter(true);

view.startAnimation(animation);

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容