自定義listview 帶下拉刷新動畫特效

android 學習中使用最多的大量數量集控件 莫過於listview ,雖然就目前來說 google 新推出一個叫recycleView的新空間,並且他本身集合了許多特性,使用起來非常方便。最主要的特性有以下幾點:
**1、控制其顯示的方式,請通過佈局管理器LayoutManager
2、控制Item間的間隔(可繪製),請通過ItemDecoration
3、控制Item增刪的動畫,請通過ItemAnimator
作者: 小灬航航
鏈接:http://www.imooc.com/article/9335
來源:慕課網** 詳細可以點擊鏈接
但是就目前來說大多數初級開發者還是更加喜歡和傾向於使用listview,如何實現listview 中的一些完美特效呢。其實,listview本身是提供了頭部和尾部的控件接口的。我們可以通過addHeaderView(View v,object data, boolean isSelectable);
addFooterView(View v,object data, boolean isSelectable);
這兩個頭部和根部的2個接口,通過自定義的view加進去。可以達到我們的效果了。當然在自定義的時候一些刷新動畫,都是通過在重寫
onTouchEvent(MotionEvent ev)這個觸摸事件來完成的。詳細內容可以看代碼。其中有較爲詳細的註釋。

import java.util.Date;
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.Animation;
import android.view.animation.AnimationUtils;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

/**
 * 自定義拉下刷新ListView,常規箭頭旋轉,系統進度條,無回彈動畫
 * @author
 * @Description
 */
public class MyCusListView extends ListView implements OnScrollListener {
    private static final String TAG = "MyCusListView===>";
    /** 操作狀態:下拉剛開始、回退到頂、一次刷新結束 */
    private static final int DONE = 0x1;
    /** 操作狀態:鬆開即可刷新 */
    private final static int RELEASE_TO_REFRESH = 0x2;
    /** 操作狀態:下拉可以刷新 */
    private final static int PULL_TO_REFRESH = 0x3;
    /** 操作狀態:正在刷新 */
    private final static int REFRESHING = 0x4;
    /** 自定義ListView頭佈局 */
    private LinearLayout headView;
    /** 刷新提示文本 */
    private TextView txtHeadTip;
    /** 最近刷新時間文本 */
    private TextView txtLastRefresh;
    /** 下拉箭頭圖標 */
    private ImageView imgRefreshArrow;
    /** 刷新進度條圖標 */
    private ProgressBar pbRefreshRound;
    /** headView寬 */
    private int headContentWidth;
    /** headView高 */
    private int headContentHeight;
    /** 下拉時箭頭旋轉動畫 */
    private Animation pullAnim;
    /** 取消時箭頭旋轉動畫 */
    private Animation reserveAnim;
    /** 標識各種刷新狀態 */
    private int refreshState;
    /** 首次觸摸屏幕設爲true,鬆手時設爲false,控制一次觸摸事件的記錄狀態 */
    private boolean isRecored = false;
    /** 手指首次觸摸屏幕時Y位置 */
    private int startY;
    /** 手指移動的距離和headView的padding距離的比例,防止移動時headView下拉過長 */
    private final static int RATIO = 3;
    /** 表示已經下拉到可以刷新狀態,可以拉回 */
    private boolean isBack = false;
    /** 刷新監聽回調接口 */
    private OnRefreshListener refreshListener;
    /** 列表在屏幕頂端第一個完整可見項的position */
    private int firstItemIndex;

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

    /** 初始化 */
    private void init(Context context) {
        Log.i(TAG, "init()...");
        // 獲取自定義頭view
        headView = (LinearLayout) LayoutInflater.from(context).inflate(
                R.layout.head_cus_listview, null);
        // 獲取headView中控件
        imgRefreshArrow = (ImageView) headView
                .findViewById(R.id.imgRefreshArrow);
        pbRefreshRound = (ProgressBar) headView
                .findViewById(R.id.pbRefreshRound);
        txtHeadTip = (TextView) headView.findViewById(R.id.txtHeadTip);
        txtLastRefresh = (TextView) headView.findViewById(R.id.txtLastRefresh);
        // 預估headView寬高
        measureView(headView);
        // 獲取headView寬高
        headContentWidth = headView.getMeasuredWidth();
        headContentHeight = headView.getMeasuredHeight();
        Log.i(TAG, "headView寬:[" + headContentWidth + "],高:["
                + headContentHeight + "]");
        // 設置headView的padding值,topPadding爲其本身的負值,達到在屏幕中隱藏的效果
        headView.setPadding(0, -headContentHeight, 0, 0);
        // 重繪headView
        headView.invalidate();
        // 將headView添加到自定義的ListView頭部
        this.addHeaderView(headView, null, false);
        // 設置ListView的滑動監聽
        this.setOnScrollListener(this);
        // 獲取箭頭旋轉動畫
        pullAnim = AnimationUtils.loadAnimation(context, R.anim.arrow_rotate);
        reserveAnim = AnimationUtils.loadAnimation(context,
                R.anim.arrow_rotate_reverse);
        // 初始刷新狀態
        refreshState = DONE;
    }

    /**
     * 預估headView的寬高
     * 
     * @param child
     */
    private void measureView(View child) {
        Log.i(TAG, "measureView()...");
        ViewGroup.LayoutParams p = child.getLayoutParams();
        if (p == null) {
            p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_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);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        // 記錄滾動時列表第一個完整可見項的position
        firstItemIndex = firstVisibleItem;
    }

    /** 監聽觸摸事件 */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:// 第一次觸摸時
            if (firstItemIndex == 0) {
                // 開始記錄
                isRecored = true;
                // 獲取首次Y位置
                startY = (int) ev.getY();
                Log.i(TAG, "從首次觸摸點就開始記錄...");
            } else {
                Log.i(TAG, "首次觸摸時firstItemIndex不爲0,不執行下拉刷新");
            }
            Log.i(TAG, "記錄狀態isRecored:" + isRecored);
            break;
        case MotionEvent.ACTION_UP:// 鬆開屏幕時
            // 移除記錄
            isRecored = false;
            Log.i(TAG, "停止記錄..." + ",isRecored:" + isRecored);
            if (refreshState == PULL_TO_REFRESH) {
                refreshState = DONE;
                changeHeadView();
                Log.i(TAG, "PULL_TO_REFRESH狀態鬆手,回到原始狀態");
            } else if (refreshState == RELEASE_TO_REFRESH) {
                refreshState = REFRESHING;
                changeHeadView();
                onRefreshing();
                Log.i(TAG, "RELEASE_TO_REFRESH狀態鬆手,進入REFRESHING狀態");
            } else if (refreshState == REFRESHING) {
                if (firstItemIndex == 0) {
                    // 保持刷新狀態
                    headView.setPadding(0, -headContentHeight, 0, 0);
                    Log.i(TAG, "REFRESHING狀態鬆手,保持該狀態,headView仍在頂部");
                } else {
                    Log.i(TAG, "REFRESHING狀態鬆手,保持該狀態,headView被推出頂部");
                }
            }
            break;
        case MotionEvent.ACTION_MOVE:// 手勢移動時
            // 記錄實時的手指移動時在屏幕的Y位置,用於和startY比較
            int curY = (int) ev.getY();

            if (!isRecored && firstItemIndex == 0) {
                isRecored = true;
                Log.i(TAG, "從移動狀態執行下拉刷新,開始記錄..." + ",isRecored:" + isRecored);
                startY = curY;
            }

            if (isRecored) {
                // 開始或結束狀態
                if (refreshState == DONE) {
                    if (curY - startY > 0) {// 表示向下拉了
                        // 狀態改爲下拉刷新
                        refreshState = PULL_TO_REFRESH;
                        changeHeadView();
                    }
                }

                // 下拉刷新狀態
                if (refreshState == PULL_TO_REFRESH) {
                    setSelection(0);
                    // 不斷改變headView的高度
                    headView.setPadding(0, (curY - startY) / RATIO
                            - headContentHeight, 0, 0);
                    // 下拉到RELEASE_TO_REFRESH狀態
                    if ((curY - startY) / RATIO >= headContentHeight * 1.5) {
                        refreshState = RELEASE_TO_REFRESH;
                        isBack = true;
                        changeHeadView();
                    } else if ((curY - startY) <= 0) {
                        // 上推到頂
                        refreshState = DONE;
                        changeHeadView();
                    }
                }

                // 鬆手可以刷新狀態
                if (refreshState == RELEASE_TO_REFRESH) {
                    setSelection(0);
                    // 不斷改變headView的高度
                    headView.setPadding(0, (curY - startY) / RATIO
                            - headContentHeight, 0, 0);
                    // 又往上推
                    if ((curY - startY) / RATIO < headContentHeight * 1.5) {
                        refreshState = PULL_TO_REFRESH;
                        changeHeadView();
                    }
                }

                // 正在刷新狀態
                if (refreshState == REFRESHING) {
                    if (curY - startY > 0) {
                        // 只改變padding值,不做其餘處理
                        headView.setPadding(0, (curY - startY) / RATIO, 0, 0);
                    }
                }
            }
            break;
        }
        return super.onTouchEvent(ev);
    }

    /** 進入刷新的方法 */
    private void onRefreshing() {
        // 調用回調接口中的刷新方法
        if (refreshListener != null) {
            refreshListener.toRefresh();
        }
    }

    /** 使用界面傳遞給此ListView的回調接口,用於兩者間通信 */
    public interface OnRefreshListener {
        public void toRefresh();
    }

    /**
     * 註冊一個用於刷新的回調接口
     * 
     * @param refreshListener
     */
    public void setOnRefreshListener(OnRefreshListener refreshListener) {
        // 獲取傳遞過來的回調接口
        this.refreshListener = refreshListener;
    }

    /** 使用界面執行完刷新操作時調用此方法 */
    public void onRefreshFinished() {
        refreshState = DONE;
        changeHeadView();
        // 顯示最近更新
        txtLastRefresh.setText("最近更新:" + new Date().toLocaleString());
    }

    /** 根據下拉的狀態改變headView */
    private void changeHeadView() {
        switch (refreshState) {
        case DONE:// 開始或結束狀態
            Log.i(TAG, "當前狀態:DONE");
            // 回退狀態清除
            isBack = false;
            // 回覆原始高度
            headView.setPadding(0, -headContentHeight, 0, 0);
            // 進度條隱藏
            pbRefreshRound.setVisibility(View.GONE);
            // 設置原始箭頭圖片
            imgRefreshArrow.setImageResource(R.drawable.indicator_arrow);
            imgRefreshArrow.setVisibility(View.VISIBLE);
            txtHeadTip.setText("下拉可以刷新...");
            break;
        case PULL_TO_REFRESH:// 下拉刷新狀態
            Log.i(TAG, "當前狀態:PULL_TO_REFRESH");
            // 從RELEASE_TO_REFRESH回到PULL_TO_REFRESH狀態
            Log.i(TAG, "是否從鬆開刷新回到下拉刷新...isBack:" + isBack);
            if (isBack) {
                // 設置箭頭回轉動畫
                imgRefreshArrow.startAnimation(reserveAnim);
            }
            txtHeadTip.setText("下拉可以刷新...");
            break;
        case RELEASE_TO_REFRESH:
            Log.i(TAG, "當前狀態:RELEASE_TO_REFRESH");
            // 設置箭頭旋轉動畫
            imgRefreshArrow.startAnimation(pullAnim);
            txtHeadTip.setText("鬆開即可刷新...");
            break;
        case REFRESHING:
            startY = 0;
            // 保持headView在屏幕頂端顯示
            // headView.setPadding(0, 0, 0, 0);
            headView.setPadding(0, -headContentHeight, 0, 0);
            headView.setVisibility(View.GONE);
            refreshState = DONE;
            // 顯示出進度條
            pbRefreshRound.setVisibility(View.GONE);
            // 隱藏箭頭圖標
            imgRefreshArrow.clearAnimation();
            imgRefreshArrow.setVisibility(View.GONE);
            txtHeadTip.setText("正在刷新...");
            Log.i(TAG, "當前狀態:REFRESHING");
            break;
        }
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        super.setAdapter(adapter);
        txtLastRefresh.setText("最近更新:" + new Date().toLocaleString());
    }
}

這個自定義控件實現了下拉箭頭,和回彈的箭頭。以及刷新時的進度更新動作,並且附帶刷新時間。
自定義headView佈局頁面代碼如下:

<?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" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >


        <LinearLayout
            android:id="@+id/layout_Text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical" >

            <TextView
                android:id="@+id/txtHeadTip"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下拉可以刷新..."
                android:textColor="@color/gray"
                android:textSize="15sp"
                android:textStyle="bold" />

            <TextView
                android:id="@+id/txtLastRefresh"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="無更新記錄"
                android:textColor="@color/lightgray"
                android:textSize="13sp" />
        </LinearLayout>


        <FrameLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginRight="20dp"
            android:layout_toLeftOf="@id/layout_Text" >

            <ImageView
                android:id="@+id/imgRefreshArrow"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:src="@drawable/indicator_arrow" />

            <ProgressBar
                android:id="@+id/pbRefreshRound"
                style="?android:attr/progressBarStyleSmall"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:visibility="gone" />
        </FrameLayout>
    </RelativeLayout>

</LinearLayout>

下面是效果圖:
刷新時的效果
下拉時的效果
鬆開的效果圖

發佈了34 篇原創文章 · 獲贊 88 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章