1. 程式人生 > >自定義ScrollView--下拉重新整理上滑底部自動載入更多

自定義ScrollView--下拉重新整理上滑底部自動載入更多

本文介紹給大家自己寫的一個方便的下拉重新整理上滑載入的自定義ScrollView;

直接上乾貨(詳解在程式碼註釋中給出):

public class RefreshScrollView extends ScrollView{
    /**
     * 重寫建構函式,這裡不是重點
     */
    public RefreshScrollView(Context context) {
        this(context,null);
    }

    public RefreshScrollView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public RefreshScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        viewWidth = getWidth(); //獲取ScrollView的寬度用來設定頭佈局的寬度
    }

    private int down_y; //按下時候的y座標
    private int scroll_y;  //ScrollView的滑動距離
    private View headViewRefresh;  //頭佈局
    private RefreshListener listsner;  //重新整理載入資料監聽
    private boolean b_down; //是否可以重新整理
    private int viewWidth;  //scrollView寬度
    private int headViewHeight;  //頭佈局重新整理時的高度

    /**
     * 設定重新整理頭佈局
     * @param view
     */
    public void setHeadView(View view){  //在引用此自定義ScrollView的activity中傳入初始化完成的頭佈局檔案
        this.headViewRefresh = view;
        this.headViewHeight = UIUtil.dip2px(getContext(),50); //dp轉px,px轉dp工具,下文給出
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) headViewRefresh.getLayoutParams();
        params.width = viewWidth;
        params.height = 0;
        headViewRefresh.setLayoutParams(params); //將headView的高度重新設定為0,也就是不可見,為什麼這麼設定?下文會介紹
    }

    /**
     * 提供給呼叫scrollView的頁面的重新整理載入回撥方法
     * @param listsner
     */
    public void setListsner(RefreshListener listsner){ //回撥介面下文給出
        this.listsner = listsner;
    }

    /**
     * 重新整理停止,給scrollView外部呼叫
     */
    public void stopRefresh() {
        listsner.hintChange("下拉重新整理");  //停止重新整理之後,將提示文字設定成初始值,時刻準備著下次重新整理
//        headViewRefresh.setVisibility(View.GONE);  //隱藏headView
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) headViewRefresh.getLayoutParams();  //將headView的高度重新設定為1
        params.width = viewWidth;
        params.height = 0;
        headViewRefresh.setLayoutParams(params); //設定頭佈局的高度為0,也就是隱藏頭佈局
    }

    @Override
    protected void onScrollChanged(int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
        super.onScrollChanged(scrollX, scrollY, oldScrollX, oldScrollY);
        scroll_y = scrollY;  //監聽賦值,監聽scrollView的滑動狀態,當滑動到頂部的時候才可以下拉重新整理
        if(scrollY == 0){

        }else if(scrollY+this.getMeasuredHeight() == this.getChildAt(0).getMeasuredHeight()){  //滑動距離+scrollView的高度如果等於scrollView的內部子view的高度則證明滑動到了底部,則自動載入更多資料
            listsner.loadMore();  //載入更多
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) { //重寫dispatchTouchEvent,敲黑板,這是重點!
        if(event.getAction() == MotionEvent.ACTION_DOWN){   //獲取手指初次觸控位置
            down_y = (int) event.getY();  //記錄下手指點下的縱座標
        }
        if(event.getAction() == MotionEvent.ACTION_MOVE){   //滑動事件
            if(scroll_y == 0){   //如果scroll_y == 0,在頂部,可以重新整理
                if(event.getY() - down_y > 0){  //手勢判斷:向下滑動,可以重新整理
                    //event.getY()-down_y是手指滑動的縱向距離,為什麼乘1/3?為了讓下拉重新整理更肉一點,這樣手指下滑300畫素,頭佈局高度增高100畫素,可根據個人喜好做出調整
                    int downRange = (int) ((event.getY()-down_y)*1/3);   //給headView動態設定高度,動態高度是手指向下滑動距離的1/3
//                    headViewRefresh.setVisibility(View.VISIBLE);  //顯示重新整理的根檢視  顯示headView控制元件
                    b_down = false;    //剛開始滑動,鬆手還不可以重新整理
                    LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) headViewRefresh.getLayoutParams();  //將滑動距離轉化後的值用來給headView動態設定高度
                    params.width = viewWidth;
                    params.height = downRange;
                    headViewRefresh.setLayoutParams(params);
                    if(downRange >= headViewHeight){   //當動態設定的高度大於初始高度的時候,變換hint,此時鬆手可重新整理;headViewHeight是我設定的初始高度50dp,也用來判斷下拉到什麼程度才能重新整理
                        listsner.hintChange("鬆開重新整理");//超過了設定的高度,可以重新整理,如果不設定這個或者設定的值太小,輕輕一拉就重新整理,體驗不好
                        b_down = true; //可以重新整理,如果此時擡起手指就可以重新整理了
                    }else{     //當動態設定的高度不大於初始高度的時候,變換hint,此時鬆手不可重新整理
                        listsner.hintChange("下拉重新整理");
                        b_down = false; //不可以重新整理
                    }
                    listsner.setWidthX((int)event.getX()); //設定觸控點的橫座標,用來優化頭佈局效果,不是非必須的
                    return true;   //攔截觸控事件,scrollView不可響應觸控事件,否則會造成鬆手滑動跳動錯位
                }else{ //手勢判斷:小於0則是上滑,此時按正常程式走
                    b_down = false; //不可以重新整理
                    return super.dispatchTouchEvent(event);  //向上滑動,不攔截
                }
            }else{ //scroll_y不等於0則是上滑,此時按正常程式走
                b_down = false; //不可以重新整理
                return super.dispatchTouchEvent(event);   //scrollView不在頂部,不攔截
            }
        }
        if(event.getAction() == MotionEvent.ACTION_UP){   //擡起手指
            if(b_down){    //如果可以重新整理
                LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) headViewRefresh.getLayoutParams();   //設定headView為原始高度
                params.width = viewWidth;
                params.height = headViewHeight;
                headViewRefresh.setLayoutParams(params);
                listsner.hintChange("正在重新整理");
                listsner.startRefresh();
            }else{    //如果不可以重新整理,停止重新整理
                stopRefresh();
            }
        }

        return super.dispatchTouchEvent(event);
    }
}

以上是我們的自定義ScrollView,裡面涉及到兩個類名:工具類UIUtil和介面RefreshListener;

public class UIUtil {
    /** dip轉換px */
    public static int dip2px(Context context, int dip) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dip * scale + 0.5f);
    }

    /** pxz轉換dip */
    public static int px2dip(Context context,int px) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (px / scale + 0.5f);
    }
}
public interface RefreshListener {
    void startRefresh(); //重新整理
    void loadMore();  //載入
    void hintChange(String hint);  //提示文字
    void setWidthX(int x);  //設定x
}

以上三個類準備就緒了,就可以開始呼叫了:

public class RefreshActivity extends Activity implements RefreshListener{ //實現scrollView的重新整理監聽,重寫四個方法
    private RefreshScrollView sv;
    private RelativeLayout headView;
    private TextView tv;
    private RefreshHeadbgView bg;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.refresh_activity);
        sv = (RefreshScrollView) findViewById(R.id.refresh_sv);
        headView = (RelativeLayout) findViewById(R.id.head_view);
        tv = (TextView) findViewById(R.id.head_view_tv);
        bg = (RefreshHeadbgView) findViewById(R.id.head_bg);
        sv.setListsner(this);
        sv.setHeadView(headView);
    }

    @Override
    public void startRefresh() {
        new Thread(new Runnable() {  //開啟子執行緒,模擬網路請求
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    handler.sendEmptyMessage(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    @Override
    public void loadMore() { //載入,同重新整理,此處就省略了,大家舉一反三吧

    }

    @Override
    public void setWidthX(int x) { //設定橫座標
//        bg.setWidthX(x);
    }

    @Override
    public void hintChange(String hint) {  //設定提示文字
        tv.setText(hint);
    }

    private Handler handler = new Handler(){  //更新scrollView
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            sv.stopRefresh(); //停止重新整理
        }
    };
}

xml檔案:以下有幾種重新整理頭佈局,需要哪種就顯示哪種,不需要的gone掉

<?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">
    <TextView
        android:layout_height="50dp"
        android:layout_width="match_parent"
        android:background="#ECECEC"
        android:gravity="center"
        android:text="標題"/>
    <com.example.view.RefreshScrollView
        android:id="@+id/refresh_sv"
        android:layout_height="match_parent"
        android:layout_width="match_parent">
        <LinearLayout
            android:layout_height="match_parent"
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:background="#00ECEC">
            <RelativeLayout  <!-- 頭佈局 -->
                android:id="@+id/head_view"
                android:layout_height="1dp"
                android:layout_width="match_parent"
                android:background="#ECEC00">

                <com.example.view.RefreshHeadbgView <!-- 自定義弧形下拉控制元件,效果圖及檔案下文給出 -->
                    android:id="@+id/head_bg"
                    android:layout_height="match_parent"
                    android:layout_width="match_parent"
                    android:visibility="gone"/>
                <ImageView  <!-- 下拉重新整理頭佈局裡可以設定圖片,效果圖下文給出 -->
                    android:layout_height="match_parent"
                    android:layout_width="match_parent"
                    android:scaleType="centerCrop"
                    android:layout_alignParentBottom="true"
                    android:visibility="visible"
                    android:src="@drawable/refreshimg"/>
                <TextView
                    android:id="@+id/head_view_tv"
                    android:layout_height="wrap_content"
                    android:layout_width="wrap_content"
                    android:text="下拉重新整理"
                    android:layout_centerInParent="true"
                    android:textColor="#ECEC00"
                    android:textSize="20sp"/>
            </RelativeLayout>
            <TextView
                android:layout_height="1000dp"
                android:layout_width="match_parent"
                android:textSize="20sp"
                android:text="1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n"/>
        </LinearLayout>
    </com.example.view.RefreshScrollView>

</LinearLayout>

RefreshHeadbgView類:

public class RefreshHeadbgView extends View {

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

    public RefreshHeadbgView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public RefreshHeadbgView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.YELLOW);

        paint.setAntiAlias(true);
        p = new Paint();
        p.setColor(0xff00ecec);
        p.setStyle(Paint.Style.FILL);
        startdp = UIUtil.dip2px(getContext(),50);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getWidth();
        height = getHeight();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = getWidth();
        height = getHeight();
    }
    private int startdp;
    private int width,height,length;
    private Paint paint;
    private Paint p;

    @Override
    protected void onDraw(Canvas canvas) {
        Path mPath = new Path();
        canvas.drawColor(0xff00ecec);
        if(height>startdp){  //高度達到一頂高度才顯示弧線
            mPath.moveTo(0, startdp); //path的用法,時間問題,這裡就不細說了
            if(useX){ //判斷有沒有設定X,也就是setWidth(x)方法
                mPath.rQuadTo(widthX, (height-startdp)*2, width, 0);
            }else{
                mPath.rQuadTo(width/2, (height-startdp)*2, width, 0);
            }
            canvas.drawPath(mPath, paint);
            canvas.drawRect(0,0,width,startdp,paint);  //填充空白
        }else{
            canvas.drawRect(0,0,width,height,paint);
        }
    }

    private int widthX;
    private boolean useX;
    public void setWidthX(int x){
        useX = true;
        widthX = x;
    }
}

好了,到這裡所有的都已完成,下面看執行結果:

           

本文到這裡就結束了,本文所有提到用到的工具類,介面類都已貼出程式碼,不要看內容多,其實很簡單。原諒我選擇的顏色,沒用大紅大綠就已經很不錯了,如有疑問歡迎留言~