Android学习(44) -- 自定义控件(8) 下拉刷新(添加header)
来源:互联网 发布:淘宝鉴权失败什么意思 编辑:程序博客网 时间:2024/06/01 13:45
1、定义HeaderView的布局
2、创建一个类去继承ListView,并重写两个构造器
3、在MainActivity中使用自定义ListView展示数据
4、在自定义ListView中将HeaderView添加到ListView上部
5、实现onTouch方法
1、自定义HeaderView
layout_header.xml
自定义ProgressBar
1、定义一个动画
<?xml version="1.0" encoding="utf-8"?><rotate xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/indicate_rotate" android:fromDegrees="0" android:pivotX="50%" android:pivotY="50%" android:toDegrees="360" ></rotate>
2、在布局中使用
<ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:indeterminate="true" android:indeterminateDuration="1000" android:indeterminateDrawable="@drawable/indeterminate_drawable" />
3、整体实现
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="horizontal" > <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:layout_marginTop="10dp" > <ImageView android:id="@+id/iv_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="@drawable/indicator_arrow" /> <ProgressBar android:id="@+id/pb_rotate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:indeterminate="true" android:indeterminateDrawable="@drawable/indeterminate_drawable" android:indeterminateDuration="1000" android:visibility="invisible" /> </RelativeLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginBottom="10dp" android:layout_marginLeft="15dp" android:layout_marginTop="10dp" android:orientation="vertical" > <TextView android:id="@+id/tv_state" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉刷新" android:textColor="#aa000000" android:textSize="20sp" /> <TextView android:id="@+id/tv_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="最后刷新:2016-5-5" android:textColor="@android:color/darker_gray" android:textSize="14sp" /> </LinearLayout></LinearLayout>
2、自定义ListView中将HeaderView添加到ListView上部
它获取到宽高,在自定义控件内部多使用这个使用view.measure(0,0)方法可以主动通知系统去测量,然后就可以直接使用它获取宽高
//获取头部的布局//final View headerView = View.inflate(MainActivity.this,//R.layout.layout_header, null);// 第 一种 获取动态 自定义组件的宽 高 //headerView.measure(0, 0); //自动的通知系统获取高度和宽度//int headerViewHeight = headerView.getMeasuredHeight();////将头部进行隐藏//headerView.setPadding(0, -headerViewHeight, 0, 0);//refLv.addHeaderView(headerView);
//第二种 获取自动加载View的高度的方式 使用监听的方式 ////使用inflate是异步的 要想获取到加载组件的高度 需要使用 getViewTreeObserver 进行监听//headerView.getViewTreeObserver().addOnGlobalLayoutListener(//new OnGlobalLayoutListener() {////@Override//public void onGlobalLayout() {//////要先remove掉一下//headerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);//////获取头部布局的高度//int headerViewHeight = headerView.getHeight();////将头部进行隐藏//headerView.setPadding(0, -headerViewHeight, 0, 0);//refLv.addHeaderView(headerView);//}//});
public class RefreshListView extends ListView {private View headerView;//HeaderViewprivate int headerViewHeight;//HeaderViewHeightpublic RefreshListView(Context context, AttributeSet attrs) {super(context, attrs);}public RefreshListView(Context context) {super(context);}/** * */private void init(){initHeaderView();}/** * 添加一个HeaderView */private void initHeaderView() {//获取头部的布局headerView = View.inflate(getContext(),R.layout.layout_header, null);//获取自定义组件的宽 高headerView.measure(0, 0);headerViewHeight = headerView.getMeasuredHeight();//将头部进行隐藏headerView.setPadding(0, -headerViewHeight, 0, 0);//将头部添加到RefreshListView中addHeaderView(headerView);}}
3、使用onTouchEvent处理下拉
1、定义一个变量 downY 用于获取按下时 Y轴的坐标
2、在ACTION_MOVE中,计算出下拉时 HeaderView的 Y轴的坐标值
用移动到的点值 减去 按下时点的值 得到移动的距离
用 -headerViewHeight 加上 得到移动距离 就是新的headerViewHeight
只有当顶部的数据是第一条Item的时候 并且 paddingTop > -headerViewHeight 的时候 进行设置 setPadding ------
用新的值 设置setPadding即可 看到HeaderView下拉的效果
3、------当下拉时,点击的是item 2下拉的时候是没有问题的,当向上滑动的时候 触发的item 为6 ,也就是说上滑的 速度明显比较快,因为ListView会自动监听垂直滑动,所以会滚动比较快,我们要想实现向下向上都是 同一个item的话 ,需要在ACTION_DOWN中设置 true, 拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动
@Overridepublic boolean onTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:downY = (int) ev.getY();break;case MotionEvent.ACTION_MOVE://获取到移动的大小int delaY = (int)ev.getY() - downY ;//计算出新的headerViewHeightint paddingTop = -headerViewHeight + delaY;if(paddingTop > -headerViewHeight && getFirstVisiblePosition() == 0){headerView.setPadding(0, paddingTop, 0, 0);//拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动return true;}break;case MotionEvent.ACTION_UP:break;}return super.onTouchEvent(ev);}
4、下拉刷新的三种状态以及动画
a、定义三个常量 并设置默认状态为下拉刷新状态
private final int PULL_REFRESH = 0;//下拉刷新的状态private final int RELEASE_REFRESH = 1;//松开刷新的状态private final int REFRESHING = 2;//正在刷新的状态private int currentState = PULL_REFRESH;
b、对子控件进行初始化,
c、 定义RotateAnimation动画 定义两个旋转动画变量, 并定义一个初始化动画的方法,用于初始化动画
//旋转动画private RotateAnimation upAnimation, downAnimation; /** * 初始化动画 */private void initRotateAnimation() {//向上的动画upAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);upAnimation.setDuration(300);upAnimation.setFillAfter(true);//向下的动画downAnimation = new RotateAnimation( -180, -360,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);downAnimation.setDuration(300);downAnimation.setFillAfter(true);}
d、定义方法用于根据状态进行更新HeaderView
当下拉刷新状态时,文本设置为 下拉刷新 ,设置向下的动画
当松开刷新状态时,文本设置为 松开刷新 ,设置向上的动画
当正在刷新时,清除动画,ImageView隐藏,ProgressBar显示, 文本设置为 正在刷新
/** * 根据 currentState 来更新 HeaderView */private void refreshHeaderView(){switch (currentState) {case PULL_REFRESH: //下拉刷新的状态tv_state.setText("下拉刷新");iv_arrow.startAnimation(downAnimation);break;case RELEASE_REFRESH: //松开刷新的状态tv_state.setText("松开刷新");iv_arrow.startAnimation(upAnimation);break;case REFRESHING://正在刷新的状态//清除动画 可能动画未执行完毕 所以需要清除动画iv_arrow.clearAnimation();//让图标隐藏iv_arrow.setVisibility(View.INVISIBLE);//ProgressBar 显示可见pb_rotate.setVisibility(View.VISIBLE);tv_state.setText("正在刷新...");break;}}
e、 在 onTouchEvent方法中 的ACTION_MOVE,
当新的HeaderViewHeight的值 paddingTop >=0 并且 当前状态是 PULL_REFRESH 的时候,由下拉刷新 进入到 松开刷新状态 ,并调用根据currentState更新UI方法refreshHeaderView()
当新的HeaderViewHeight的值 paddingTop< 0 并且 当前状态是 RELEASE_REFRESH的时候,由松开刷新 进入到 下拉刷新状态 并调用根据currentState更新UI方法refreshHeaderView()
在 onTouchEvent方法中 的ACTION_UP,有两种状态
a、当currentState为下拉刷新状态,则隐藏HeaderView
b、当 currentState为松开刷新状态,则显示HeaderView,并且将currentState重置为刷新中的状态,并调用根据currentState更新UI的方法
在 onTouchEvent方法中 的ACTION_MOVE,当currentState 为REFRESHING的时候,终止操作。
public boolean onTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:downY = (int) ev.getY();break;case MotionEvent.ACTION_MOVE://如果在刷新中的话 就不让操作了if(currentState == REFRESHING){break;}//获取到移动的大小int delaY = (int)(ev.getY() - downY) ;//计算出新的headerViewHeightint paddingTop = -headerViewHeight + delaY;if(paddingTop > -headerViewHeight && getFirstVisiblePosition() == 0){headerView.setPadding(0, paddingTop, 0, 0);if(paddingTop >= 0 &¤tState == PULL_REFRESH){//下拉刷新状态 进入松开刷新状态currentState = RELEASE_REFRESH;refreshHeaderView();}else if(paddingTop < 0&& currentState == RELEASE_REFRESH){//松开刷新状态 进入下拉刷新状态currentState = PULL_REFRESH;refreshHeaderView();}//拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动return true;}break;case MotionEvent.ACTION_UP:if(currentState == PULL_REFRESH){//隐藏HeaderViewheaderView.setPadding(0, -headerViewHeight, 0, 0);}else if(currentState == RELEASE_REFRESH){headerView.setPadding(0, 0, 0, 0);currentState = REFRESHING;refreshHeaderView();//模拟延时//new Handler().postDelayed(new Runnable() {////@Override//public void run() {//completeRefresh();//}//}, 3000); }break;}return super.onTouchEvent(ev);}
f、完成刷新后,重置下拉刷新状态,要在请求完数据,更新Adapter之后,在UI线程中调用该方法,在e中 模拟调用了该方法 ---ACTION_UP中
/** * 完成刷新 重置为下拉刷新状态 * 在请求完数据 更新Adapter之后 在UI线程中调用该方法 */public void completeRefresh(){//隐藏HeaderViewheaderView.setPadding(0, -headerViewHeight, 0, 0);//状态改变currentState = PULL_REFRESH;//ProgressBar隐藏pb_rotate.setVisibility(View.INVISIBLE);//图片显示iv_arrow.setVisibility(View.VISIBLE);tv_state.setText("下拉刷新");tv_time.setText("最后刷新: " + getCurrentTime());}/** * 获取当前时间 * @return */private String getCurrentTime(){SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss");return format.format(new Date());}
5、对外提供一个接口,用于处理刷新过程中业务逻辑请求的处理
/** * 定义刷新监听接口,用于处理刷新过程中业务逻辑请求的处理 * @author Denny * @date 2016-4-26 */public interface OnRefreshListener{//下来刷新的时候调用void onPullRefresh();}private OnRefreshListener listener;public void setOnRefreshListener( OnRefreshListener listener){this.listener = listener;}
4、在MainActivity中实现调用
1、定义一个模拟请求数据 通过Handler更新UI的方法 在Handler更新UI后 重置状态
private Handler handler = new Handler(){ public void handleMessage(android.os.Message msg) { //更新UI myAdapter.notifyDataSetChanged();//重置状态refLv.completeRefresh(); };}; //模拟向服务器请求数据private void requestDataFromServer(){new Thread(){public void run() {//模拟请求服务的一个时间长度SystemClock.sleep(3000);//将数据添加到头部list.add(0, "下拉刷新的数据");//更新UIhandler.sendEmptyMessage(0);};}.start();}
2、通过调用setOnRefreshListener来实现 请求服务器数据,然后更新UI
refLv.setOnRefreshListener(new OnRefreshListener() {public void onPullRefresh() {//Log.e("MainActivity", "进入正在刷新状态,此时应请求服务器数据");//需要联网请求服务器数据,然后更新UIrequestDataFromServer();}});
完整代码
MainActivity
public class MainActivity extends Activity {private RefreshListView refLv;private ArrayList<String> list = new ArrayList<String>();private MyAdapter myAdapter;private Handler handler = new Handler(){ public void handleMessage(android.os.Message msg) { //更新UI myAdapter.notifyDataSetChanged();//重置状态refLv.completeRefresh(); };};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);initView();initData();}/** * 初始化View */private void initView() {setContentView(R.layout.activity_main);refLv = (RefreshListView) findViewById(R.id.refLv);}/** * 初始化数据 */private void initData() {for (int i = 0; i < 15; i++) {list.add("ListView原来数据 :" + i);}//获取头部的布局//final View headerView = View.inflate(MainActivity.this,//R.layout.layout_header, null);//1、第一种 获取自动加载View的高度的方式 使用监听的方式 ////使用inflate是异步的 要想获取到加载组件的高度 需要使用 getViewTreeObserver 进行监听//headerView.getViewTreeObserver().addOnGlobalLayoutListener(//new OnGlobalLayoutListener() {////@Override//public void onGlobalLayout() {//////要先remove掉一下//headerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);//////获取头部布局的高度//int headerViewHeight = headerView.getHeight();////将头部进行隐藏//headerView.setPadding(0, -headerViewHeight, 0, 0);//refLv.addHeaderView(headerView);//}//});// 第 一种 获取动态 自定义组件的宽 高 //headerView.measure(0, 0); //自动的通知系统获取高度和宽度//int headerViewHeight = headerView.getMeasuredHeight();////将头部进行隐藏//headerView.setPadding(0, -headerViewHeight, 0, 0);//refLv.addHeaderView(headerView);myAdapter = new MyAdapter();refLv.setAdapter(myAdapter);refLv.setOnRefreshListener(new OnRefreshListener() {public void onPullRefresh() {//Log.e("MainActivity", "进入正在刷新状态,此时应请求服务器数据");//需要联网请求服务器数据,然后更新UIrequestDataFromServer();}});}//模拟向服务器请求数据private void requestDataFromServer(){new Thread(){public void run() {//模拟请求服务的一个时间长度SystemClock.sleep(3000);//将数据添加到头部list.add(0, "下拉刷新的数据");//更新UIhandler.sendEmptyMessage(0);};}.start();}class MyAdapter extends BaseAdapter {@Overridepublic int getCount() {return list.size();}@Overridepublic Object getItem(int position) {return null;}@Overridepublic long getItemId(int position) {return 0;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {TextView tv = new TextView(MainActivity.this);tv.setPadding(20, 20, 20, 20);tv.setTextSize(18);tv.setText(list.get(position));return tv;}}}
public class RefreshListView extends ListView {private View headerView;//HeaderViewprivate int headerViewHeight;//HeaderViewHeightprivate int downY ;//按下时 的 Y坐标点 private final int PULL_REFRESH = 0;//下拉刷新的状态private final int RELEASE_REFRESH = 1;//松开刷新的状态private final int REFRESHING = 2;//正在刷新的状态private int currentState = PULL_REFRESH;private ImageView iv_arrow;private ProgressBar pb_rotate;private TextView tv_state;private TextView tv_time;//旋转动画private RotateAnimation upAnimation, downAnimation; public RefreshListView(Context context, AttributeSet attrs) {super(context, attrs);init();}public RefreshListView(Context context) {super(context);init();}/** * 初始化ListView中的数据 */private void init(){//初始化HeaderViewinitHeaderView();//初始化动画initRotateAnimation();}/** * 初始化动画 */private void initRotateAnimation() {//向上的动画upAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);upAnimation.setDuration(300);upAnimation.setFillAfter(true);//向下的动画downAnimation = new RotateAnimation( -180, -360,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);downAnimation.setDuration(300);downAnimation.setFillAfter(true);}/** * 添加一个HeaderView */private void initHeaderView() {//获取头部的布局headerView = View.inflate(getContext(),R.layout.layout_header, null);iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);pb_rotate = (ProgressBar) headerView.findViewById(R.id.pb_rotate);tv_state = (TextView) headerView.findViewById(R.id.tv_state);tv_time = (TextView) headerView.findViewById(R.id.tv_time);//获取自定义组件的宽 高headerView.measure(0, 0);headerViewHeight = headerView.getMeasuredHeight();//将头部进行隐藏headerView.setPadding(0, -headerViewHeight, 0, 0);//将头部添加到RefreshListView中addHeaderView(headerView);}@Overridepublic boolean onTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:downY = (int) ev.getY();break;case MotionEvent.ACTION_MOVE://如果在刷新中的话 就不让操作了if(currentState == REFRESHING){break;}//获取到移动的大小int delaY = (int)(ev.getY() - downY) ;//计算出新的headerViewHeightint paddingTop = -headerViewHeight + delaY;if(paddingTop > -headerViewHeight && getFirstVisiblePosition() == 0){headerView.setPadding(0, paddingTop, 0, 0);if(paddingTop >= 0 &¤tState == PULL_REFRESH){//下拉刷新状态 进入松开刷新状态currentState = RELEASE_REFRESH;refreshHeaderView();}else if(paddingTop < 0&& currentState == RELEASE_REFRESH){//松开刷新状态 进入下拉刷新状态currentState = PULL_REFRESH;refreshHeaderView();}//拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动return true;}break;case MotionEvent.ACTION_UP:if(currentState == PULL_REFRESH){//隐藏HeaderViewheaderView.setPadding(0, -headerViewHeight, 0, 0);}else if(currentState == RELEASE_REFRESH){headerView.setPadding(0, 0, 0, 0);currentState = REFRESHING;refreshHeaderView();//模拟延时//new Handler().postDelayed(new Runnable() {////@Override//public void run() {//completeRefresh();//}//}, 3000);if(listener != null){listener.onPullRefresh();}}break;}return super.onTouchEvent(ev);}/** * 根据 currentState 来更新 HeaderView */private void refreshHeaderView(){switch (currentState) {case PULL_REFRESH: //下拉刷新的状态tv_state.setText("下拉刷新");iv_arrow.startAnimation(downAnimation);break;case RELEASE_REFRESH: //松开刷新的状态tv_state.setText("松开刷新");iv_arrow.startAnimation(upAnimation);break;case REFRESHING://正在刷新的状态//清除动画 可能动画未执行完毕 所以需要清除动画iv_arrow.clearAnimation();//让图标隐藏iv_arrow.setVisibility(View.INVISIBLE);//ProgressBar 显示可见pb_rotate.setVisibility(View.VISIBLE);tv_state.setText("正在刷新...");break;}}/** * 完成刷新 重置为下拉刷新状态 * 在请求完数据 更新Adapter之后 在UI线程中调用该方法 */public void completeRefresh(){//隐藏HeaderViewheaderView.setPadding(0, -headerViewHeight, 0, 0);//状态改变currentState = PULL_REFRESH;//ProgressBar隐藏pb_rotate.setVisibility(View.INVISIBLE);//图片显示iv_arrow.setVisibility(View.VISIBLE);tv_state.setText("下拉刷新");tv_time.setText("最后刷新: " + getCurrentTime());}/** * 获取当前时间 * @return */private String getCurrentTime(){SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss");return format.format(new Date());}/** * 定义刷新监听接口,用于处理刷新过程中业务逻辑请求的处理 * @author Denny * @date 2016-4-26 */public interface OnRefreshListener{//下来刷新的时候调用void onPullRefresh();}private OnRefreshListener listener;public void setOnRefreshListener( OnRefreshListener listener){this.listener = listener;}}
- Android学习(44) -- 自定义控件(8) 下拉刷新(添加header)
- Android学习(45) -- 自定义控件(9) 下拉刷新 (添加footer)
- android 自定义ListView下拉刷新控件——自定义控件学习(五)
- 自定义控件 CustomListView (下拉刷新)
- swift3自定义下拉刷新控件(一)
- android —— 自定义控件 最简单下拉刷新,Google最新(可刷新任何控件)
- android 自定义ListView实现下拉刷新、分页加载、点击事件——自定义控件学习(七)
- Android自定义控件下拉刷新
- Android控件(一)下拉刷新:SwipeRefreshLayout
- android 下拉刷新控件PtrClassicFrameLayout(cube)
- 修改SwipeRefreshLayout源码实现自定义Header的下拉刷新控件
- 仿京东下拉刷新动画的源码查看——自定义控件学习(一)
- 自定义控件(四)-下拉刷新与上拉加载
- 自定义下拉刷新上拉加载控件(SwipeRefreshLayout + recyclerView)
- swift3自定义下拉刷新控件(二)QQ弹簧效果
- Android自定义控件(一) 下拉刷新,上拉分页加载更多(支持ListView, GridView, ScrollView)
- Android自定义控件(二) 下拉刷新,上拉分页加载更多(支持ListView, GridView, ScrollView)
- Android自定义控件(三)下拉刷新类的常用逻辑
- bean validation 分组验证及分组顺序
- 剑指offer(15):调整数组顺序使奇数位于偶数前面
- 对“视觉机器学习20讲配套仿真代码”的研究心得---贝叶斯学习
- Hive的三种调用方式
- video 视屏基础
- Android学习(44) -- 自定义控件(8) 下拉刷新(添加header)
- Bool和bool
- [教程] 卡尔曼滤波简介及其算法实现代码(C++/C/MATLAB)
- 配置WebLogic Server 11g 集群、Session复制(Windows)
- 统计学生成绩
- Map
- Linux性能监控工具dstat
- <OJ_Sicily>1152简单的马周游问题
- 使用Caffe复现DeepID实验