自定義ListView實現下拉刷新,下拉加載的功能

package com.loaderman.myrefreshlistviewdemo;


import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * 實現步驟:
 * 一、給ListView添加頭佈局
 * 二、默認讓ListView的頭佈局隱藏起來
 * 負的paddingTop的值
 * 如何獲取頭佈局的高度
 * 三、慢慢的將頭佈局拖出來
 * 獲取在ListView中的滑動偏移量--onTouchEvent
 * 關於起點座標的獲取dispatchTouchEvent
 * 四、給RefreshListView定義了三種狀態
 * refreshUi:根據當前的狀態刷新控件的顯示
 * 在狀態發生改變的時候來調用此方法便可
 * 五、增長了動畫效果
 * clearAnimation的使用
 * 六、處理up的事件
 * STATE_PULL_TO_REFRESH的時候up
 * 隱藏頭佈局
 * STATE_RELEASE_TO_REFRESH的時候up
 * 顯示頭佈局
 * 更新狀態--STATE_REFRESHING
 * 通知觀察者去加載數據
 * 七、觀察者設計模式的使用
 * 找出被觀察者
 * 定義觀察者接口,接口中的方法就是觀察者感興趣的事件
 * 在被觀察中存儲觀察者的引用
 * 在事件發生的時候,通知觀察者
 * 爲何要用接口而不使用抽象類--單繼承,多實現
 * 八、由TabDetailPager來通知RefreshListView數據加載完成
 * setOnRefreshComplete
 * 更新狀態,隱藏頭佈局
 * 九、設置時間的顯示
 * 存在sp中
 * 十、自定義ProgressBar的效果
 * 上拉加載:
 * 一、添加腳佈局,默認隱藏
 * 二、增長了滾動監聽,
 * idle,顯示最後一個條目的時候,顯示腳佈局
 * 三、通知觀察者加載下一頁的數據
 * 四、加載下一頁數據的邏輯
 * 將下一頁數據的集合添加到上一頁數據的集合紅,不能new Adapter
 * 五、TabDetailPager通知ListView下一頁數據加載完成
 * 重置isLoadingMore
 * 隱藏腳佈局
 */

public class RefreshListView extends ListView {

    public static final int STATE_PULL_TO_REFRESH    = 0;
    public static final int STATE_RELEASE_TO_REFRESH = 1;
    public static final int STATE_REFRESHING         = 2;

    private int mCurrentState = STATE_PULL_TO_REFRESH;//定義ListView當前的狀態

    private float           startY;
    private int             headerViewHeight;
    private View            headerView;
    private ImageView       ivArrow;
    private ProgressBar     pb;
    private TextView        tvTips;
    private TextView        tvDate;
    private RotateAnimation downAnimation;
    private RotateAnimation upAnimation;
    private View            footerView;
    private int             footerViewHeight;

    public RefreshListView(Context context) {
        this(context, null);
    }

    public RefreshListView(Context context, AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initHeaderView();
        initAnimation();
        initFooterView();
    }


    private void initAnimation() {
        upAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        upAnimation.setFillAfter(true);
        upAnimation.setDuration(200);
        downAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        downAnimation.setFillAfter(true);
        downAnimation.setDuration(200);

    }

    private void initHeaderView() {
        //頭佈局越早添加,位於越上邊
        headerView = View.inflate(getContext(), R.layout.layout_refresh_header, null);
        ivArrow = (ImageView) headerView.findViewById(R.id.ivArrow);
        pb = (ProgressBar) headerView.findViewById(R.id.pb);
        tvTips = (TextView) headerView.findViewById(R.id.tvTips);
        tvDate = (TextView) headerView.findViewById(R.id.tvDate);

        String lastUpdateTime = PrefUtils.getString(getContext(), "lastUpdateTime", "");
        tvDate.setText(lastUpdateTime);
        //設置一個控件的高度或者寬度的信息得找LayoutParams
        //若是設置一個負的padding的值,只能在代碼中設置纔會其效果
        //measure-layout-draw
        //千萬不要在Activity的onCreate方法中獲取一個控件的寬度或者高度或者位置信息
        //監聽視圖樹
        /*headerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {

            }
        });*/
        //手動測量
        headerView.measure(0, 0);//將測量的工做交給系統來完成,咱們不參與任何的限制的意見
        //獲取測量以後的寬度或者高度信息
        headerViewHeight = headerView.getMeasuredHeight();
        headerView.setPadding(0, -headerViewHeight, 0, 0);
        this.addHeaderView(headerView);
    }

    private boolean isLoadingMore = false;

    private void initFooterView() {
        footerView = View.inflate(getContext(), R.layout.layout_refresh_footer, null);
        footerView.measure(0, 0);
        footerViewHeight = footerView.getMeasuredHeight();
        footerView.setPadding(0, -footerViewHeight, 0, 0);
        this.addFooterView(footerView);

        //給ListView增長一個監聽
        this.setOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                int lastVisiblePosition = getLastVisiblePosition();
                if (scrollState == SCROLL_STATE_IDLE && lastVisiblePosition == getCount() - 1 && !isLoadingMore) {
                    //System.out.println("到底了...");
                    Log.i("RefreshListView", "到底了...");
                    isLoadingMore = true;
                    //將腳佈局顯示出來
                    footerView.setPadding(0, 0, 0, 0);
                    //自動滑到腳佈局的位置,讓腳佈局能夠一會兒就可以看得見
                    setSelection(getCount() - 1);

                    notifyLoadMore();//通知觀察者去加載下一頁的數據

                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            }
        });
    }

    //一旦事件到達了一個控件上,必定,最早,會調用dispatchTouchEvent
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            startY = ev.getY();//在這個父控件獲得事件的時候,就把起點座標初始化,這樣就不會受制於子控件是否消費了事件,起點座標就會很精確了
        }
        return super.dispatchTouchEvent(ev);
    }

    //onTouchEvent的來源:
    //一、自身攔截   二、子控件回傳
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                startY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:

                if (mCurrentState == STATE_REFRESHING) {
                    break;
                }

                float moveY = ev.getY();
                float dy = moveY - startY;
                //什麼狀況下須要把頭佈局拖出來
                int firstVisiblePosition = getFirstVisiblePosition();
                //一、下拉  二、顯示的第0個條目是下拉刷新頭佈局
                if (dy > 0 && firstVisiblePosition == 0) {
                    int paddingTop = (int) (dy - headerViewHeight);
                    headerView.setPadding(0, paddingTop, 0, 0);

                    int oldState = mCurrentState;
                    if (paddingTop < 0) {
                        //頭佈局有一部分沒有顯示出來
                        mCurrentState = STATE_PULL_TO_REFRESH;
                    } else {
                        mCurrentState = STATE_RELEASE_TO_REFRESH;
                    }

                    //在狀態發生改變的時候才須要刷新UI
                    if (oldState != mCurrentState) {
                        refreshUi();
                    }


                    return true;//表明消費了事件
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mCurrentState == STATE_PULL_TO_REFRESH) {
                    //將頭佈局隱藏起來
                    headerView.setPadding(0, -headerViewHeight, 0, 0);
                } else if (mCurrentState == STATE_RELEASE_TO_REFRESH) {
                    //改變當前的狀態,刷新控件
                    mCurrentState = STATE_REFRESHING;
                    refreshUi();
                    //將頭佈局徹底顯示出來
                    headerView.setPadding(0, 0, 0, 0);
                    //去重寫加載網絡上的數據
                    //tabDetailPager.getDataFromServer();
                    notifyRefresh();
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

    public void setOnRefreshComplete(boolean success) {
        //一、更新當前的狀態
        mCurrentState = STATE_PULL_TO_REFRESH;
        pb.setVisibility(View.INVISIBLE);
        ivArrow.setVisibility(View.VISIBLE);
        tvTips.setText("下拉刷新");
        //二、隱藏頭佈局
        headerView.setPadding(0, -headerViewHeight, 0, 0);

        if (success) {
            //更新tvDate的顯示
            setCurrentDate();
        }

    }

    private void setCurrentDate() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String currentDate = sdf.format(new Date());
        tvDate.setText(currentDate);

        PrefUtils.setString(getContext(), "lastUpdateTime", currentDate);
    }

    public void setOnLoadMoreComplete() {
        isLoadingMore = false;
        //隱藏腳佈局
        footerView.setPadding(0, -footerViewHeight, 0, 0);

    }


    //定義觀察者接口
    public interface OnRefreshListener {
        public void onRefresh();

        public void onLoadMore();
    }

    //保存觀察者的實例對象
    private OnRefreshListener listener;

    public void setOnRefreshListener(OnRefreshListener listener) {
        this.listener = listener;
    }

    //通知觀察者
    private void notifyRefresh() {
        if (listener != null) {
            listener.onRefresh();
        }
    }

    private void notifyLoadMore() {
        if (listener != null) {
            listener.onLoadMore();
        }
    }

    /*private TabDetailPager tabDetailPager;

    public void setTabDetailPager(TabDetailPager tabDetailPager) {
        this.tabDetailPager = tabDetailPager;
    }*/

    private void refreshUi() {
        switch (mCurrentState) {
            case STATE_PULL_TO_REFRESH:
                pb.setVisibility(View.INVISIBLE);//INVISIBLE會佔位,GONE不會佔位
                ivArrow.setVisibility(View.VISIBLE);
                tvTips.setText("下拉刷新");
                ivArrow.startAnimation(downAnimation);
                break;
            case STATE_RELEASE_TO_REFRESH:
                pb.setVisibility(View.INVISIBLE);
                ivArrow.setVisibility(View.VISIBLE);
                ivArrow.startAnimation(upAnimation);
                tvTips.setText("鬆開刷新");
                break;
            case STATE_REFRESHING:
                pb.setVisibility(View.VISIBLE);
                ivArrow.clearAnimation();//要控制一個控件的可見度的時候,須要先移除以前設置過的動畫
                ivArrow.setVisibility(View.INVISIBLE);
                tvTips.setText("正在刷新");
                break;
        }
    }
}

 layout_refresh_footer.xmljava

<?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:gravity="center"
    android:orientation="horizontal"
    >

    <ProgressBar
        android:id="@+id/pb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminateDrawable="@drawable/shape_progress"/>

    <TextView
        android:id="@+id/tvTips"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="正在加載"
        android:textColor="#F00"
        android:textSize="16sp"/>

</LinearLayout>

 layout_refresh_header.xmlandroid

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

    <FrameLayout
        android:layout_margin="5dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

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

        <ProgressBar
            android:id="@+id/pb"
            android:visibility="invisible"
            android:indeterminateDrawable="@drawable/shape_progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    </FrameLayout>

    <LinearLayout
        android:layout_margin="5dp"
        android:gravity="center"
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="wrap_content"
            android:text="下拉刷新"
            android:id="@+id/tvTips"
            android:textColor="#F00"
            android:textSize="16sp"
            android:layout_height="wrap_content"/>

        <TextView
            android:layout_width="wrap_content"
            android:text="2016-12-17"
            android:id="@+id/tvDate"
            android:textColor="#ccc"
            android:textSize="12sp"
            android:layout_height="wrap_content"/>

    </LinearLayout>


</LinearLayout>

 shape_progress.xml設計模式

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
        android:toDegrees="720"
        android:pivotY="50%"
        android:pivotX="50%"
    >
    <shape
           android:innerRadius="15dp"
           android:shape="ring"
           android:thickness="3dp"
           android:useLevel="false"
        >
        <!--<solid android:color="@android:"-->
        <gradient
            android:startColor="#f00"
            android:centerColor="#af00"
            android:endColor="#fff"
            />
    </shape>
</rotate>

 

package com.loaderman.myrefreshlistviewdemo;

import android.content.Context;
import android.content.SharedPreferences;

/**
 * 關於SharedPreference的工具類
 */

public class PrefUtils {
    public static String getString(Context context,String key,String defValue) {
        SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
        String retString = sp.getString(key, defValue);
        return retString;
    }
    public static void setString(Context context,String key ,String value) {
        SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
        SharedPreferences.Editor edit = sp.edit();
        edit.putString(key, value);
        edit.commit();
    }
}

 代碼使用:網絡

package com.loaderman.myrefreshlistviewdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Random;

public class MainActivity extends AppCompatActivity implements RefreshListView.OnRefreshListener {

    private RefreshListView lvListNews;
    private ArrayList mList;
    private MyListAdapter myListAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mList = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            mList.add("我是天才" + i + "號");
        }
        lvListNews = (RefreshListView) findViewById(R.id.lvListNews);
        lvListNews.setOnRefreshListener(this);
        lvListNews.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

            }
        });
        myListAdapter = new MyListAdapter();
        lvListNews.setAdapter(myListAdapter);
    }
    //下拉刷新
    @Override
    public void onRefresh() {
        final Random random = new Random();
        mList.add(0, "我是天才" + random.nextInt(100) + "號");
        Toast.makeText(MainActivity.this, "刷新了一條數據", Toast.LENGTH_SHORT).show();
        //刷新完成
        lvListNews.setOnRefreshComplete(true);
        myListAdapter.notifyDataSetChanged();
    }
    //上拉加載
    @Override
    public void onLoadMore() {
        // 添加數據
        for (int i = 30; i < 35; i++) {
            mList.add("我是天才" + i+ "號");
            // 這裏要放在裏面刷新,放在外面會致使刷新的進度條卡住
            myListAdapter.notifyDataSetChanged();
        }
         //加載完成
        lvListNews.setOnLoadMoreComplete();
        Toast.makeText(MainActivity.this, "加載了" + 5 + "條數據", Toast.LENGTH_SHORT).show();
    }
    class MyListAdapter extends BaseAdapter {


        @Override
        public int getCount() {
            return mList.size();
        }

        @Override
        public Object getItem(int position) {
            return mList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if(convertView == null){
                convertView = View.inflate(MainActivity.this, R.layout.item_news_tab_detail, null);
                holder = new ViewHolder();
                holder.tvContent = (TextView) convertView.findViewById(R.id.tvContent);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.tvContent.setText(mList.get(position)+"");
            return convertView;
        }
    }

    static class ViewHolder {
        TextView tvContent;
    }

}

 activity_main.xmlapp

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.loaderman.myrefreshlistviewdemo.MainActivity">
    <com.loaderman.myrefreshlistviewdemo.RefreshListView
        android:id="@+id/lvListNews"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
       />
</RelativeLayout>

 效果圖:dom

相關文章
相關標籤/搜索