自定义ListView实现下拉刷新和分页加载(效果类似知乎)

摘要:

自定义了一个ListView,实现下拉刷新和分页加载。


下拉刷新效果:

当ListView滑到最顶端的时候,向下滑动手指,ListView并不向下滑动,而是在顶端出现一个headerBar,headerBar的宽度与手指向下滑动的距离成正比,手指滑动距离超过阈值,ListView通过接口回调,通知外部进行refresh,刷新内容,同时headerBar变为从左向右不断滑动的动画。

方案:

通过Override ListView的onTouchEvent(MotionEvent ev)方法实现。


分页加载效果:

当ListView滑动到最底端并停止滑动时,ListView通过接口回调,通知外部进行LoadMore,加载更多。

方案:

实现OnScrollListener接口,Override onScrollStateChanged方法,和onScroll方法


xml代码如下,HeaderView用于下拉刷新,FooterView用于分页加载

</pre><pre name="code" class="html"><?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/abc_action_bar_default_padding_material"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/drag_header"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:background="@android:color/holo_blue_light"
        android:visibility="invisible"/>

    <LinearLayout
        android:id="@+id/refresh_header"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        android:visibility="invisible">

        <TextView
            android:layout_width="0px"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@android:color/holo_blue_light"/>
        <TextView
            android:layout_width="0px"
            android:layout_height="match_parent"
            android:layout_weight="0.5"/>
        <TextView
            android:layout_width="0px"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@android:color/holo_blue_light"/>
        <TextView
            android:layout_width="0px"
            android:layout_height="match_parent"
            android:layout_weight="0.5"/>
        <TextView
            android:layout_width="0px"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@android:color/holo_blue_light"/>
        <TextView
            android:layout_width="0px"
            android:layout_height="match_parent"
            android:layout_weight="0.5"/>
        <TextView
            android:layout_width="0px"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@android:color/holo_blue_light"/>
        <TextView
            android:layout_width="0px"
            android:layout_height="match_parent"
            android:layout_weight="0.5"/>
        <TextView
            android:layout_width="0px"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@android:color/holo_blue_light"/>

    </LinearLayout>

</FrameLayout>

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tv_footer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="Loading"/>

</FrameLayout>


Java代码如下

package com.lihao.widget;

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.TextView;
import android.util.AttributeSet;
import android.R.color;

import com.lihao.R;

public class RefreshAndLoadListView extends ListView implements OnScrollListener {

    Context context;
    LayoutInflater inflater;
    OnRefreshAndLoadListener listener;
    FrameLayout.LayoutParams params;

    FrameLayout header;
    FrameLayout footer;
    TextView drag_header;
    TextView tv_footer;
    LinearLayout refresh_header;

    TranslateAnimation animation;

    boolean isRefreshing = false;
    boolean isLoading = false;
    boolean isLoadingOver = false;
    boolean pressFromTop = true;
    boolean isLastItemVisible;

    int pressPosition;
    int distanceDown;
    int firstVisibleItem;

    int listViewWidth;

    private final int HEADER_HEIGHT = 10;   // in pix
    private final int FOOTER_HEIGHT = 100;  // in pix
    private final int ANIM_DURATION = 300;  // in milliseconds
    private final int DRAG_THRESHOLD = 400; // in pix
    private final float ANIM_OFFSET = (1 + 0.5f) / (5 + 4*0.5f) / 2;

    public RefreshAndLoadListView(Context context) {
        super(context);
        init(context);
    }

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

    public RefreshAndLoadListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }
    
    private void init(Context context) {

        this.context = context;
        inflater = LayoutInflater.from(context);

        header = (FrameLayout) inflater.inflate(R.layout.lv_refresh_and_load_header, null);
        footer = (FrameLayout) inflater.inflate(R.layout.lv_refresh_and_load_footer, null);
        drag_header = (TextView) header.findViewById(R.id.drag_header);
        tv_footer = (TextView) footer.findViewById(R.id.tv_footer);
        refresh_header = (LinearLayout) header.findViewById(R.id.refresh_header);
        this.addHeaderView(header);
        this.addFooterView(footer);

        setFooterDividersEnabled(false);
        setHeaderDividersEnabled(false);

        params = (FrameLayout.LayoutParams) drag_header.getLayoutParams();
        params.height = HEADER_HEIGHT;
        drag_header.setLayoutParams(params);
        params = (FrameLayout.LayoutParams) tv_footer.getLayoutParams();
        params.height = FOOTER_HEIGHT;
        tv_footer.setLayoutParams(params);
        params = (FrameLayout.LayoutParams) refresh_header.getLayoutParams();
        params.height = HEADER_HEIGHT;
        refresh_header.setLayoutParams(params);

        this.setOnScrollListener(this);

        animation =  new TranslateAnimation(
                Animation.RELATIVE_TO_PARENT, -ANIM_OFFSET,
                Animation.RELATIVE_TO_PARENT, ANIM_OFFSET,
                Animation.RELATIVE_TO_PARENT, 0f,
                Animation.RELATIVE_TO_PARENT, 0f);
        animation.setRepeatCount(Animation.INFINITE);
        animation.setRepeatMode(Animation.RESTART);
        animation.setInterpolator(new LinearInterpolator());
        animation.setDuration(ANIM_DURATION);
        animation.setFillAfter(false);
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                pressFromTop = (header.getTop() == 0) && (firstVisibleItem == 0);
                pressPosition = (int) ev.getY();
                if (animation.hasStarted()) {
                    refresh_header.clearAnimation();
                    refresh_header.setVisibility(INVISIBLE);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (pressFromTop) {
                    setPressed(false);
                    for (int i = 0; i < ev.getHistorySize(); i++) {
                        distanceDown = (int) ev.getHistoricalY(i) - pressPosition;
                        if (!isRefreshing) {
                            onPreRefresh(distanceDown);
                            if (distanceDown > DRAG_THRESHOLD) {
                                onRefresh();
                                return true;
                            }
                        }
                    }
                    if (distanceDown > 0) {
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (pressFromTop) {
                    distanceDown = 0;
                    onPreRefresh(distanceDown);
                }
        }

        return super.onTouchEvent(ev);
    }

    private void onPreRefresh(int distanceDown) {
        if (isRefreshing) return;
        drag_header.setVisibility(VISIBLE);
        header.setBackgroundColor(color.white);
        refresh_header.setVisibility(INVISIBLE);
        listViewWidth = header.getMeasuredWidth();
        params = (FrameLayout.LayoutParams) drag_header.getLayoutParams();
        params.width = distanceDown * listViewWidth / DRAG_THRESHOLD;
        drag_header.setLayoutParams(params);
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == SCROLL_STATE_IDLE && isLastItemVisible) {
            onLoad();
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        this.firstVisibleItem = firstVisibleItem;
        isLastItemVisible = ((firstVisibleItem + visibleItemCount) == totalItemCount);
    }

    /**
     * 设置listener
     */
    public void setOnRefreshAndLoadListener(OnRefreshAndLoadListener listener) {
        this.listener = listener;
    }

    /**
     * 外部通知ListView数据更新的状态
     */
    public void onRefreshComplete() {
        isRefreshing = false;
        refresh_header.clearAnimation();
        refresh_header.setVisibility(INVISIBLE);
    }

    public void onLoadComplete() {
        isLoading = false;
    }

    public void onLoadOver() {
        isLoadingOver = true;
        tv_footer.setText("No More Data.");
    }

    public void onError() {
        tv_footer.setText("Unknown Error.");
    }


    /**
     * ListView 通过 callback interface 发出更新数据的指令
     */
    private void onRefresh() {

        if (isRefreshing) return;
        isRefreshing = true;

        drag_header.setVisibility(INVISIBLE);
        refresh_header.setVisibility(VISIBLE);
        refresh_header.startAnimation(animation);

        if (listener != null) {
            listener.onRefresh();
        }
    }

    private void onLoad() {

        if (isLoading || isLoadingOver) return;
        isLoading = true;

        if (listener != null) {
            listener.onLoad();
        }
    }

    /**
     * callback interface
     */
    public interface OnRefreshAndLoadListener {
        public void onRefresh();
        public void onLoad();
    }
}



作者:李浩。

转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值