Android自定義View-下拉刷新控件

Android自定義View-下拉刷新控件

   下拉刷新是android開發過程中很常見的功能,github上面有許多下拉刷新的開源控件可以使用。但有時候這些開源控件不能完全符合我們的項目要求,這時就需要自己進行修改,這時候我們就需要了解下拉刷新的原理,才能自由的修改它的功能,因此我自己寫了一個簡單的下拉刷新控件,以瞭解其原理

下拉刷新原理

下拉刷新控件主要由兩部分組成,內容部分,與下拉頭部分

其主要流程:初始時將下拉頭的位置設置到屏幕上方,當內容部分滑動到頂部時則根據手指滑動的y軸方向參數,將下拉頭滑動到屏幕中,當滑動距離達到某一個值時則可以刷新,這時手指鬆開,則進入刷新狀態,刷新完成之後回到初始位置

初始化狀態

實現

大概原理已經清楚了,那我們就要開始實現一個下拉刷新控件了
先寫一個下拉頭的佈局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp">

    <ImageView
        android:id="@+id/arrow"
        android:layout_width="wrap_content"
        android:layout_height="60dp"
        android:layout_centerInParent="true"
        android:src="@mipmap/arrow"
        />

    <ProgressBar
        android:id="@+id/progress_bar"
        android:layout_width="30dip"
        android:layout_height="30dip"
        android:layout_centerInParent="true"
        android:visibility="gone"
        />

</RelativeLayout>

然後新建一個RefreshableView的類,這個繼承自LinearLayout,之所以繼承LinearLayout是因爲我們的下拉刷新控件是一個線性佈局的ViewGroup

添加下拉頭佈局

public RefreshableView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //添加下拉頭佈局
        header = LayoutInflater.from(context).inflate(R.layout.pull_to_refresh , null , true);
        arrow = (ImageView)header.findViewById(R.id.arrow);
        progressBar = (ProgressBar)header.findViewById(R.id.progress_bar);
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        setOrientation(VERTICAL);
        addView(header , 0);

        mScroller = new Scroller(context);
}

在Layout時設置下拉頭的初始位置

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if(changed && !loadOnce){
            //下拉頭向上偏移,隱藏下拉頭
            hideHeaderHeight = -header.getHeight();
            ((MarginLayoutParams)header.getLayoutParams()).topMargin = hideHeaderHeight;
            //設置ListView的觸摸事件
            listView = (ListView)getChildAt(1);
            listView.setOnTouchListener(this);
            loadOnce = true;
        }
}

ListView滑動監聽的onTouch方法

 @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        //判斷ListView是否滑動到頂部
        isAbleToPull(motionEvent);
        if(ableToPull){
            switch (motionEvent.getAction()){
                case MotionEvent.ACTION_DOWN:
                    yDown = motionEvent.getRawY();
                    mLastY = motionEvent.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    //手指上滑不處理事件
                    if(motionEvent.getRawY() - yDown < 0 && getScrollY() == 0){
                        return false;
                    }
                    //小於最小滑動距離
                    if(motionEvent.getRawY() - yDown < touchSlop){
                        return false;
                    }
                    //手指是下滑並且下拉頭完全隱藏執行下滑
                    if(motionEvent.getRawY() - yDown > 0 && getScrollY() >= 0){
                        //滑動距離大於下拉頭高度鬆手可刷新
                        if(getScrollY() < hideHeaderHeight){
                            currentStatus = STATUS_RELEASE_TO_REFRESH;
                        }else{
                            currentStatus = STATUS_PULL_TO_REFRESH;
                        }
                        scrollBy(0 , -(int)(motionEvent.getRawY() - mLastY));
                    }
                    mLastY = motionEvent.getRawY();
                    break;
                case MotionEvent.ACTION_UP:
                default:
                    if(currentStatus == STATUS_PULL_TO_REFRESH){
                        //隱藏下拉頭
                        smoothScrollBy( 0 , -getScrollY());
                    }
                    if(currentStatus == STATUS_RELEASE_TO_REFRESH){
                        //刷新
                        onRefresh();
                    }
                    break;
            }
            // 時刻記得更新下拉頭中的信息
            if (currentStatus == STATUS_PULL_TO_REFRESH
                    || currentStatus == STATUS_RELEASE_TO_REFRESH) {
                updateHeaderView();
                // 當前正處於下拉或釋放狀態,要讓ListView失去焦點,否則被點擊的那一項會一直處於選中狀態
                listView.setPressed(false);
                listView.setFocusable(false);
                listView.setFocusableInTouchMode(false);
                lastStatus = currentStatus;
                // 當前正處於下拉或釋放狀態,通過返回true屏蔽掉ListView的滾動事件
                return true;
            }
        }
        return false;
    }

isAbleToPull方法判斷ListView是否滑動到頂部

public void isAbleToPull(MotionEvent motionEvent){
        View fristChiler = listView.getChildAt(0);
        if(fristChiler != null) {
            //如果ListView滑動到頂部可以下拉下拉頭
            if (listView.getFirstVisiblePosition() == 0 && fristChiler.getTop() == 0){
                if(!ableToPull) {
                    yDown = motionEvent.getRawY();
                }
                ableToPull = true;
            }else{
                ableToPull = false;
            }
        }else{
            //ListView爲空,也可以下拉下拉頭
            ableToPull = true;
        }
}

rotateArrow方法根據當前狀態旋轉箭頭

private void rotateArrow() {
        float pivotX = arrow.getWidth() / 2f;
        float pivotY = arrow.getHeight() / 2f;
        float fromDegrees = 0f;
        float toDegrees = 0f;
        if (currentStatus == STATUS_PULL_TO_REFRESH) {
            fromDegrees = 180f;
            toDegrees = 360f;
        } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
            fromDegrees = 0f;
            toDegrees = 180f;
        }
        RotateAnimation animation = new RotateAnimation(fromDegrees, toDegrees, pivotX, pivotY);
        animation.setDuration(100);
        animation.setFillAfter(true);
        arrow.startAnimation(animation);
    }

updateHeaderView根據當前狀態更新下拉頭的信息

private void updateHeaderView() {
        if (lastStatus != currentStatus) {
            if (currentStatus == STATUS_PULL_TO_REFRESH) {
                arrow.setVisibility(View.VISIBLE);
                progressBar.setVisibility(View.GONE);
                rotateArrow();
            } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
                arrow.setVisibility(View.VISIBLE);
                progressBar.setVisibility(View.GONE);
                rotateArrow();
            } else if (currentStatus == STATUS_REFRESHING) {
                progressBar.setVisibility(View.VISIBLE);
                arrow.clearAnimation();
                arrow.setVisibility(View.GONE);
            }
        }
    }

這段代碼實現平滑滑動

    public void smoothScrollBy(int dx , int dy){
        mScroller.startScroll( 0 , getScrollY() , 0 , dy , SCROLL_TIME);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if(mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX() , mScroller.getCurrY());
            postInvalidate();
        }
    }

定義一個下拉刷新監聽藉口

 public interface PullToRefreshListener {

        /**
         * 刷新時會去回調此方法,在方法內編寫具體的刷新邏輯。注意此方法是在子線程中調用的, 你可以不必另開線程來進行耗時操作。
         */
        void onRefresh();

    }

刷新完成方法,設置刷新事件監聽器,刷新方法

 //刷新結束後調用的方法
    public void finishRefresh(){
        currentStatus = STATUS_REFRESH_FINISHED;
        smoothScrollBy( 0 , -getScrollY());
    }
    //設置刷新事件監聽
    public void setRefreshListener(PullToRefreshListener l){
        this.mListener = l;
    }
    //刷新方法
    public void onRefresh(){
        smoothScrollBy( 0 , (-getScrollY()) + hideHeaderHeight);
        if(mListener != null){
            mListener.onRefresh();
        }
    }

控件已經寫完了,接下來我們要看一下實現效果如果
在activity佈局文件中加入如下佈局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <com.example.pulltorefreshtest.RefreshableView
        android:id="@+id/refreshable_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >

        <ListView
            android:id="@+id/list_view"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:scrollbars="none" >
        </ListView>
    </com.example.pulltorefreshtest.RefreshableView>

</RelativeLayout>

在MainActivity中設置下拉刷新事件

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        refreshableView = (RefreshableView) findViewById(R.id.refreshable_view);
        listView = (ListView) findViewById(R.id.list_view);
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items);
        listView.setAdapter(adapter);
        refreshableView.setRefreshListener(new RefreshableView.PullToRefreshListener() {
            @Override
            public void onRefresh() {
                refreshableView.finishRefresh();
            }
        });
    }

源碼地址:https://github.com/GameT/PullToRefreshDemo

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