Android自定义ListView下拉刷新

实现的目标是本地有数据并没有刷新。下拉数据及时刷新数据。

我在网上找了某位写的MyListView,这个东西的下拉核心部分还是没有弄明白。非常感谢这位作者。

XML布局文件源代码:

<?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="match_parent"
    android:orientation="vertical" >

    <RelativeLayout
        android:id="@+id/head_contentLayout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="30dp" >

        <!-- 箭头图像、进度条 -->
        <FrameLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true" >

            <!-- 箭头 -->
            <ImageView
                android:id="@+id/head_arrowImageView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:src="@drawable/arrow" />

            <!-- 进度条 -->
            <ProgressBar
                android:id="@+id/head_progressBar"
                style="?android:attr/progressBarStyleSmall"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:visibility="gone" />
        </FrameLayout>

        <!-- 提示、最近更新 -->
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:gravity="center_horizontal"
            android:orientation="vertical" >

            <!-- 提示 -->
            <TextView
                android:id="@+id/head_tipsTextView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下拉刷新"
                android:textColor="#D1EEEE"
                android:textSize="20sp" />

            <!-- 最近更新 -->
            <TextView
                android:id="@+id/head_lastUpdatedTextView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="上次更新"
                android:textColor="#D1EEEE"
                android:textSize="12sp" />
        </LinearLayout>
    </RelativeLayout>

</LinearLayout>

自定义MyListView源代码:

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.AbsListView.OnScrollListener;

public class MyListView  extends ListView implements OnScrollListener {

    private static final String TAG = "listview";

    private final static int RELEASE_To_REFRESH = 0;//释放刷新
    private final static int PULL_To_REFRESH = 1;//向上拉刷新
    private final static int REFRESHING = 2;//刷新中
    private final static int DONE = 3;//已经完成
    private final static int LOADING = 4;//加载中

    private LayoutInflater inflater = null;//对于一个没有被载入或者想要动态载入的界面
    private LinearLayout headView;//线性布局
    private ImageView arrowImageView = null;//箭头图片

    private TextView tipsTextview;
    private TextView lastUpdatedTextView;
    private ProgressBar progressBar;

    private int headContentWidth;//head的宽度
    private int headContentHeight;//head的高度

    private RotateAnimation animation;
    private RotateAnimation reverseAnimation;

    private int firstItemIndex;

    /**
     *  记录ListView的状态信息,起始状态为DONE
     */
    private int state;
    /**
     *  控制是否能够刷新,默认为false
     */
    private boolean isRefreshable;

    // 实际的padding的距离与界面上偏移距离的比例
    private final static int RATIO = 3;
    // 用于保证startY的值在一个完整的touch事件中只被记录一次
    private boolean isRecored;
    private int startY;
    private boolean isBack;

    private OnRefreshListener refreshListener;

    public interface OnRefreshListener {
        public void onRefresh();
    }

    private void onRefresh() {
        if (refreshListener != null) {
            refreshListener.onRefresh();
        }
    }

    public MyListView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    public MyListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        /**
         * ListView是常用的显示控件,默认背景是和系统窗口一样的透明色,如果给ListView加上背景图片,或者背景颜色时,滚动时listView会黑掉,
         * 原因是,滚动时,列表里面的view重绘时,用的依旧是系统默认的透明色,颜色值为#FF191919
         * 要改变这种情况,listView的属性 Android:cacheColorHint="#00000000"即可
         */
        setCacheColorHint(context.getResources().getColor(R.color.transparent));
        inflater = LayoutInflater.from(context);
        headView = (LinearLayout) inflater.inflate(R.layout.utils_head, null);
        /**
         * 设置箭头图片默认是向下
         */
        arrowImageView = (ImageView) headView.findViewById(R.id.head_arrowImageView);
        arrowImageView.setMinimumWidth(70);
        arrowImageView.setMinimumHeight(50);

        progressBar = (ProgressBar) headView.findViewById(R.id.head_progressBar);
        tipsTextview = (TextView) headView.findViewById(R.id.head_tipsTextView);
        lastUpdatedTextView = (TextView) headView.findViewById(R.id.head_lastUpdatedTextView);
        /**
         * 此方法直接照搬自网络上的一个下拉刷新的demo,此处是“估计”headView的width以及height
         */
        measureView(headView);
        headContentHeight = headView.getMeasuredHeight();
        headContentWidth = headView.getMeasuredWidth();
        headView.setPadding(0, -1 * headContentHeight, 0, 0);
        headView.invalidate();
        /**
         * Header添加到ListView头部,然后设置一个Adapter
         */
        addHeaderView(headView, null, false);
        /**
         * 监听滚动条
         */
        setOnScrollListener(this);
        /**
         * 设置一些旋转的动画
         */
        animation = new RotateAnimation(0, -180,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        animation.setInterpolator(new LinearInterpolator());
        animation.setDuration(250);
        animation.setFillAfter(true);
        reverseAnimation = new RotateAnimation(-180, 0,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        reverseAnimation.setInterpolator(new LinearInterpolator());
        reverseAnimation.setDuration(200);
        reverseAnimation.setFillAfter(true);

        state = DONE;
        isRefreshable = false;

    }

    /**
     * 重载 AbsListView中的onTouchEvent事件
     *
     * 参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。
     * 返回值:该方法的返回值机理与键盘响应事件的相同,同样是当已经完整地处理了该事件且不希望其他回调方法再次处理时返回true,否则返回false。
     *
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isRefreshable) {
            //得到触摸的类型
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    /**
                     * firstVisibleItem:当前能看见的第一个列表项ID(从0开始)
                     * 意思就是往下拉的时候没有isRecored而且能看到一个的列表项时记录startY
                     */
                    if (firstItemIndex == 0 && !isRecored) {
                        isRecored = true;
                        startY = (int) event.getY();
                        Log.v(TAG, "在down时候记录当前位置‘");
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    if (state != REFRESHING && state != LOADING) {
                        if (state == DONE) {
                            // 什么都不做
                        }
                        if (state == PULL_To_REFRESH) {
                            state = DONE;
                            changeHeaderViewByState();
                            Log.v(TAG, "由下拉刷新状态,到done状态");
                        }
                        if (state == RELEASE_To_REFRESH) {
                            state = REFRESHING;
                            changeHeaderViewByState();
                            onRefresh();
                            Log.v(TAG, "由松开刷新状态,到done状态");
                        }
                    }
                    isRecored = false;
                    isBack = false;
                    break;
                case MotionEvent.ACTION_MOVE:
                    int tempY = (int) event.getY();
                    /**
                     * 手指移动过程中tempY数据会不断变化,当滑动到firstItemIndex,即到达顶部,
                     * 需要记录手指所在屏幕的位置: startY = tempY ,后面作位置比较使用
                     * 如果手指继续向下推,tempY继续变化,当tempY-startY>0,即是需要显示header部分
                     * 此时需要更改状态:state = PULL_To_REFRESH
                     */
                    if (!isRecored && firstItemIndex == 0) {
                        Log.v(TAG, "在move时候记录下位置");
                        isRecored = true;
                        startY = tempY;
                    }
                    if (state != REFRESHING && isRecored && state != LOADING) {
                        // 保证在设置padding的过程中,当前的位置一直是在head,否则如果当列表超出屏幕的话,当在上推的时候,列表会同时进行滚动
                        // 可以松手去刷新了
                        if (state == RELEASE_To_REFRESH) {
                            setSelection(0);
                            // 往上推了,推到了屏幕足够掩盖head的程度,但是还没有推到全部掩盖的地步
                            if (((tempY - startY) / RATIO < headContentHeight)
                                    && (tempY - startY) > 0) {
                                state = PULL_To_REFRESH;
                                changeHeaderViewByState();
                                Log.v(TAG, "由松开刷新状态转变到下拉刷新状态");
                            }
                            // 一下子推到顶了
                            else if (tempY - startY <= 0) {
                                state = DONE;
                                changeHeaderViewByState();
                                Log.v(TAG, "由松开刷新状态转变到done状态");
                            }
                              // 往下拉了,或者还没有上推到屏幕顶部掩盖head的地步
                            else {
                                // 不用进行特别的操作,只用更新paddingTop的值就行了
                            }
                        }
                        // 还没有到达显示松开刷新的时候,DONE或者是PULL_To_REFRESH状态
                        if (state == PULL_To_REFRESH) {
                            setSelection(0);
                            /**
                             * 下拉到可以进入RELEASE_TO_REFRESH的状态
                             * 等于headContentHeight时,即是正好完全显示header部分
                             * 大于headContentHeight时,即是超出header部分更多
                             * 当header部分能够完全显示或者超出显示,
                             * 需要更改状态: state = RELEASE_To_REFRESH
                             */
                            if ((tempY - startY) / RATIO >= headContentHeight) {
                                state = RELEASE_To_REFRESH;
                                isBack = true;
                                changeHeaderViewByState();
                                Log.v(TAG, "由done或者下拉刷新状态转变到松开刷新");
                            }
                            // 上推到顶了
                            else if (tempY - startY <= 0) {
                                state = DONE;
                                changeHeaderViewByState();
                                Log.v(TAG, "由DOne或者下拉刷新状态转变到done状态");
                            }
                        }

                        // done状态下
                        if (state == DONE) {
                            /**
                             * 手指移动过程中tempY数据会不断变化,当滑动到firstItemIndex,即到达顶
                             * 需要记录手指所在屏幕的位置: startY = tempY ,后面作位置比较使用
                             * 如果手指继续向下推,tempY继续变化,当tempY-startY>0,即是需要显示header部分
                             * 此时需要更改状态:state = PULL_To_REFRESH
                             */
                            if (tempY - startY > 0) {
                                state = PULL_To_REFRESH;
                                changeHeaderViewByState();
                            }
                        }

                        // 更新headView的size
                        if (state == PULL_To_REFRESH) {
                            headView.setPadding(0, -1 * headContentHeight
                                    + (tempY - startY) / RATIO, 0, 0);
                        }

                        // 更新headView的paddingTop
                        if (state == RELEASE_To_REFRESH) {
                            headView.setPadding(0, (tempY - startY) / RATIO
                                    - headContentHeight, 0, 0);
                        }

                    }
                    break;
            }
        }
        return super.onTouchEvent(event);
    }

    //滚动时一直回调,直到停止滚动时才停止回调。单击时回调一次。
    public void onScroll(AbsListView view, int     firstVisibleItem, int visibleItemCount, int totalItemCount) {
        firstItemIndex = firstVisibleItem;
    }

    //正在滚动时回调,回调2-3次,手指没抛则回调2次。scrollState = 2的这次不回调
    //回调顺序如下
    //第1次:scrollState = SCROLL_STATE_TOUCH_SCROLL(1) 正在滚动
    //第2次:scrollState = SCROLL_STATE_FLING(2) 手指做了抛的动作(手指离开屏幕前,用力滑了一下)
    //第3次:scrollState = SCROLL_STATE_IDLE(0) 停止滚动
    public void onScrollStateChanged(AbsListView view, int scrollState) {
    }  

    /**
     * 当状态改变时候,调用该方法,以更新界面
     */
    private void changeHeaderViewByState() {
        switch (state) {
        case RELEASE_To_REFRESH:
            arrowImageView.setVisibility(View.VISIBLE);
            progressBar.setVisibility(View.GONE);
            tipsTextview.setVisibility(View.VISIBLE);
            lastUpdatedTextView.setVisibility(View.VISIBLE);
            arrowImageView.clearAnimation();
            arrowImageView.startAnimation(animation);
            tipsTextview.setText("松开刷新");
            Log.v(TAG, "当前状态,松开刷新");
            break;
        case PULL_To_REFRESH:
            progressBar.setVisibility(View.GONE);
            tipsTextview.setVisibility(View.VISIBLE);
            lastUpdatedTextView.setVisibility(View.VISIBLE);
            arrowImageView.clearAnimation();
            arrowImageView.setVisibility(View.VISIBLE);
            // 是由RELEASE_To_REFRESH状态转变来的
            if (isBack) {
                isBack = false;
                arrowImageView.clearAnimation();
                arrowImageView.startAnimation(reverseAnimation);
                tipsTextview.setText("下拉刷新");
            } else {
                tipsTextview.setText("下拉刷新");
            }
            Log.v(TAG, "当前状态,下拉刷新");
            break;

        case REFRESHING:
            headView.setPadding(0, 0, 0, 0);
            progressBar.setVisibility(View.VISIBLE);
            arrowImageView.clearAnimation();
            arrowImageView.setVisibility(View.GONE);
            tipsTextview.setText("正在刷新...");
            lastUpdatedTextView.setVisibility(View.VISIBLE);
            Log.v(TAG, "当前状态,正在刷新...");
            break;
        case DONE:
            headView.setPadding(0, -1 * headContentHeight, 0, 0);
            progressBar.setVisibility(View.GONE);
            arrowImageView.clearAnimation();
            arrowImageView.setImageResource(R.drawable.arrow);
            tipsTextview.setText("下拉刷新");
            lastUpdatedTextView.setVisibility(View.VISIBLE);
            Log.v(TAG, "当前状态,done");
            break;
        }
    }

    private void measureView(View child) {
        ViewGroup.LayoutParams p = child.getLayoutParams();
        if (p == null) {
            p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
        int lpHeight = p.height;
        int childHeightSpec;
        if (lpHeight > 0) {
            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
        }
        child.measure(childWidthSpec, childHeightSpec);
    }

}

使用MyListView:

private MyListView myListView = null;
myListView=(MyListView)((Activity) context).findViewById(R.id.listview);

private void RefreshListener(){
    myListView.setonRefreshListener(new OnRefreshListener() {
        public void onRefresh() {
            new AsyncTask<Void, Void, Void>() {
                protected Void doInBackground(Void... params) {
                    try {
                        Thread.sleep(8000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return null;
                }
                @Override
                protected void onPostExecute(Void result) {
                    myAdapter.notifyDataSetChanged();
                    myListView.onRefreshComplete();
                }
            }.execute((Void)null);
            Log.e("Informacation","下拉刷新");
            //doSomeThings
        }
    });
}

Android自定义ListView下拉刷新

时间: 2024-10-10 22:40:41

Android自定义ListView下拉刷新的相关文章

Android中ListView下拉刷新的实现

ListView中的下拉刷新是非常常见的,也是经常使用的,看到有很多同学想要,那我就整理一下,供大家参考.那我就不解释,直接上代码了. 这里需要自己重写一下ListView,重写代码如下: [java] view plain copy package net.loonggg.listview; import java.util.Date; import android.content.Context; import android.util.AttributeSet; import androi

【转载】Android中ListView下拉刷新的实现

在网上看到一个下拉刷新的例子,很的很棒,转载和更多的人分享学习 原文:http://blog.csdn.net/loongggdroid/article/details/9385535 ListView中的下拉刷新是非常常见的,也是经常使用的,看到有很多同学想要,那我就整理一下,供大家参考.那我就不解释,直接上代码了. 这里需要自己重写一下ListView,重写代码如下: [java] view plaincopy package net.loonggg.listview; import jav

Android实现ListView下拉刷新和上拉加载更多的思路

1.继承ListView类来扩展实现 2.ListView类可以通过addFooterView和addHeaderView方法来添加列表的头和尾,可以用来实现一些拉动的动画和提示 3.重载public boolean onTouchEvent(MotionEvent ev)方法,可以监听到触摸点的点下.滑动和离开的操作,用来判断滑动的方向 4. public void onScrollStateChanged(AbsListView view, int scrollState)事件,监控列表的滚

android中listView下拉刷新

Android的ListView是应用最广的一个组件,功能强大,扩展性灵活(不局限于ListView本身一个类),前面的文章有介绍分组,拖拽,3D立体,游标,圆角,而今天我们要介绍的是另外一个扩展ListView:下拉刷新的ListView.    下拉刷新界面最初流行于iphone应用界面,如图:     然后在Android中也逐渐被应用,比如微博,资讯类.    所以,今天要实现的结果应该也是类似的,先贴出最终完成效果,如下图,接下来我们一步一步实现. 1. 流程分析    下拉刷新最主要

ListView下拉刷新、上拉载入更多之封装改进

在Android中ListView下拉刷新.上拉载入更多示例一文中,Maxwin兄给出的控件比较强大,前面有详细介绍,但是有个不足就是,里面使用了一些资源文件,包括图片,String,layout,这样不利于封装打包,下面我将源码进行改进,所有布局全部用代码实现,这样直接将src和assets打包成jar就成了一个非常方便以后使用的扩展ListView控件,代码如下: XListView: /** * @file XListView.java * @package me.maxwin.view

Android—自定义控件实现ListView下拉刷新

这篇博客为大家介绍一个android常见的功能——ListView下拉刷新(参考自他人博客,网址忘记了,阅读他的代码自己理解注释的,希望能帮助到大家): 首先下拉未松手时候手机显示这样的界面: 下面的代码是自定的扎样的控件: package com.dhsr.smartID.view; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import andr

Android ListView 下拉刷新 点击加载更多

最近项目中用到了ListView的下拉刷新的功能,总结了一下前辈们的代码,单独抽取出来写了一个demo作为示例. 效果图 下拉刷新: 加载更多: CustomListView.java [java] view plaincopy package com.example.uitest.view; import java.util.Date; import com.example.uitest.R; import android.content.Context; import android.uti

基于Android的计步器(Pedometer)的讲解(六)——ListView下拉刷新页面

计步器(Pedometer)整个项目的源代码,最近做了比较大的修改,可能以前下载的不能运行,感兴趣的朋友可以下载来看看(记得帮小弟在github打个星~) https://github.com/296777513/pedometer 今天实现实现的下拉刷新的功能,先上几张效果图: 如图所示,今天就是要实现的这个效果 首先,分析ListView下拉刷新实现方式 1.需要添加顶部下拉加载页面 2.需要监听onScrollListener来判断当前是否显示在listview的最顶部 3.因为顶部下拉加

Android中实现下拉刷新

需求:项目中的消息列表界面要求实现类似sina微博的下拉刷新: 思路:一般的消息列表为ListView类型,将list加载到adapter中,再将adapter加载到 ListView中,从而实现消息列表的展示.而下拉刷新要求给消息列表加一个头部,其中有图片(向上/向下箭头)和提示字样(下拉刷新/松开刷新),从 而我们需要做的事情:1.需要做一个head.xm来实现头部的样式定义:2.需要自定义一个继承了ListView的MsgListView,在该类中 将head加在MsgListView对象