1. 程式人生 > >上拉重新整理和下拉重新整理的實現

上拉重新整理和下拉重新整理的實現

先來兩張效果圖

         

關於下拉重新整理,Google提供了一個佈局SwipRefreshLayout,它裡面可以包涵一個滑動控制元件,然後你可以設定它的重新整理事件就OK了,非常簡單用。但是上拉重新整理就有點麻煩了。網上很多方法都是給recyclerview新增footer的方法,我也採用這種方法實現了一個。其實也就是recyclerview的item多佈局。一般佈局和footer佈局。,在上滑到最後一個item的時候,就把加一個footer型別的item,這裡,然後很明顯這裡也就行滑動監聽,就監聽它上滑動到最後一個item。下面先。下面直接給程式碼

。匯入依賴。由於wish我是用了用OKhttp就是實現的聯網重新整理,所以匯入了OKhttp的依賴包,當然還得加聯網許可權。

 compile 'com.android.support:recyclerview-v7:25.3.1'
    compile 'com.squareup.okhttp:okhttp:2.4.0'
貼個footer的佈局,因為footer包含兩個部分“正在載入:”,和沒有“沒有更多了,”。用用的時候根據情況選擇隱藏、顯示或者gone
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content">


    <LinearLayout
        android:id="@+id/line1"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:gravity="center">

        <ProgressBar
            android:layout_width="40dp"
            android:layout_height="40dp"
            />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:gravity="center_vertical"
            android:layout_marginLeft="5dp"
            android:text="正在載入..."
            android:textSize="20dp"/>

    </LinearLayout>

    <LinearLayout
        android:id="@+id/line2"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:gravity="center">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="沒有更多了!"
            android:textSize="20sp"/>

    </LinearLayout>

</LinearLayout>

最核心的兩個類adapter和監聽。
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
    private ArrayList<String> list;
    private final int ITEM=0;//載入的為一般item
    private final int FOOT=1;//載入footer

    //上拉載入的狀態
    private int loadstate=2;//預設載入完成
    public final int LOADING = 1;//正在載入
    public final int FINISH = 2;// 載入完成
    public final int END = 3;  //沒有更多資料了(顯示另一footer)

    public MyAdapter(ArrayList<String> list) {
        this.list = list;
    }

    public void setLoadstate(int loadstate) {//用於動態設定載入狀態
        this.loadstate = loadstate;
        notifyDataSetChanged();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //通過載入型別,來顯示不同的view。viewType由getItemViewType確定
        if (viewType == ITEM) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item, parent, false);
            return new ItemViewHodler(view);

        } else if (viewType == FOOT) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.foot, parent, false);
            return new FootViewHodler(view);
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        //繫結viewholder的時候根據型別來繫結
        if (holder instanceof ItemViewHodler) {
            ItemViewHodler itemhodler= (ItemViewHodler) holder;
            itemhodler.textView.setText(list.get(position));
        } else if (holder instanceof FootViewHodler) {
            FootViewHodler foothoder = (FootViewHodler) holder;
            switch (loadstate){
                case LOADING:
                    foothoder.line1.setVisibility(View.VISIBLE);
                    foothoder.line2.setVisibility(View.GONE);
                    break;
                case FINISH:
                    //設定成INVISIBLE而不是gone,為了給footer預留位置,美觀
                    foothoder.line1.setVisibility(View.INVISIBLE);
                    foothoder.line2.setVisibility(View.GONE);
                    break;
                case END:
                    foothoder.line1.setVisibility(View.GONE);
                    foothoder.line2.setVisibility(View.VISIBLE);
                    break;
            }
        }

    }

    @Override
    public int getItemViewType(int position) {
        //最後一個載入footer,
        if (position + 1 == getItemCount()) {
            return FOOT;
        } else {
            return ITEM;
        }
    }

    @Override
    public int getItemCount() {
        return list.size()+1;//因為要加上一個footer
    }

    

    private class ItemViewHodler extends RecyclerView.ViewHolder{

        TextView textView;
        public ItemViewHodler(View itemView) {
            super(itemView);
            textView= (TextView) itemView.findViewById(R.id.text);
        }
    }

    private class FootViewHodler extends RecyclerView.ViewHolder{

        LinearLayout line1;
        LinearLayout line2;
        public FootViewHodler(View itemView) {
            super(itemView);
            line1= (LinearLayout) itemView.findViewById(R.id.line1);
            line2= (LinearLayout) itemView.findViewById(R.id.line2);
        }
    }
}

public abstract class FootScrollListener extends RecyclerView.OnScrollListener {
    //通過重寫RecyclerView的滑動監聽來判斷是否滑動到底部
    private boolean isScrollUp=false;

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        /*newState表示當前滑動的狀態
        SCROLL_STATE_IDLE:不滑動
        SCROLL_STATE_DRAGGING;滑動(手指在螢幕上)
        SCROLL_STATE_SETTLING;滑動(手指移開螢幕)
        */
        LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
        // 當不滑動時
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            //返回最後一個完成可見檢視位置
            int lastItemPosition = manager.findLastCompletelyVisibleItemPosition();
            int itemCount = manager.getItemCount();
            // 判斷是否滑動到了最後一個item,並且是向上滑動
            if (lastItemPosition == (itemCount - 1) && isScrollUp) {
                // 載入更多
                myLoad();
            }
        }
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        /*
        這個方法實時監測頁面(不是手指)滑動距離
        dx表示橫向滑動距離
        dy表示縱向
        大於0向上,小於0向下,等於0不滑動
        */
        isScrollUp= dy>=0;

    }
    //這裡可以用介面回撥,然後就不用將此類設定成抽象類,
    //設定一個回撥,
    public abstract void myLoad();
}

最後主活動類
public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private SwipeRefreshLayout refreshLayout;
    private MyAdapter myAdapter;
    private ArrayList<String> list=new ArrayList<String>();
    private static boolean isloading=false;//判斷是否在載入

    private Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if(msg.what==1){
                refreshLayout.setEnabled(true);
                if (refreshLayout != null && refreshLayout.isRefreshing()) {
                            refreshLayout.setRefreshing(false);
                }
                myAdapter.setLoadstate(myAdapter.FINISH);
                isloading=false;
                smooth();
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initvew();
        somelisten();
    }

    public void initvew(){
        refreshLayout= (SwipeRefreshLayout) findViewById(R.id.refresh);
        //關於SwipeRefreshLayout的其他屬性和方法,自行查閱文件

        recyclerView= (RecyclerView) findViewById(R.id.recycle);
        for(int i=0;i<25;i++) list.add("item");
        myAdapter=new MyAdapter(list);

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        //recyclerView.setLayoutManager(new GridLayoutManager(this,3));
        recyclerView.setAdapter(myAdapter);
    }

    public void somelisten(){
        refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                //避免下拉重新整理與上拉重新整理衝突(雖然可能性很小),進行遮蔽,保證只有一個重新整理
                if(!isloading){
                    isloading=true;
                    list.add(0,"newitem");
                    httppost();
                }
            }
        });

        recyclerView.addOnScrollListener(new FootScrollListener() {
            @Override
            public void myLoad() {
                /*
                SwipeRefreshLayout重新整理載入的時候遮蔽了下拉重新整理事件,避免同時重新整理多次
                但是我們自定義的上拉重新整理不能自己遮蔽,我們這裡用靜態變數來實現遮蔽功能
                */
                if(!isloading){
                    Log.e("重新整理","進入");
                    myAdapter.setLoadstate(myAdapter.LOADING);
                    isloading=true;
                    refreshLayout.setEnabled(false);//上拉重新整理的時候遮蔽掉下拉重新整理
                    if (list.size() < 28) {
                        //網路請求
                        //list.add("lastitem");
                        httppost();


                    } else {
                        // 顯示載入到底的提示
                        myAdapter.setLoadstate(myAdapter.END);
                    }
                }
            }
        });
    }

    //recyclerview滑動到適當位置
    public void smooth(){
        /*getChildAt返回組中指定位置的檢視,組是指螢幕顯示的item組
        *getTop返回此檢視相對於其父項頂部的位置
        *smoothScrollBy沿任意軸給定的畫素滑動
        *關於recyclerview滑動最常用的是smoothScrollToPosition,不做講解
        * */
        int top=recyclerView.getChildAt(0).getTop();
        Log.e("top", String.valueOf(top));
        recyclerView.smoothScrollBy(0,top);
        Log.e("count", String.valueOf(recyclerView.getChildCount()));
    }

    public void httppost(){
        final OkHttpClient client=new OkHttpClient();
        RequestBody formBody = new FormEncodingBuilder()
                .add("passwd","123")
                .add("number","123")
                .build();
        final Request request=new Request.Builder()
                .url("http://10.126.149.157:8080/Test/HelloServlet")
                .post(formBody)
                .build();
        new Thread(){
            @Override
            public void run() {
                Response response = null;
                try {
                    response = client.newCall(request).execute();
                    if (response.isSuccessful()) {
                        Log.e("結果", response.body().string());
                        handler.sendEmptyMessage(1);
                    }
                    else {
                        Log.e("結果", "請求出錯");
                        handler.sendEmptyMessage(1);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    Log.e("結果", "丟擲異常");
                    handler.sendEmptyMessageDelayed(1,500);
                }
            }
        }.start();
    }
}

我這裡是直接拿了以前的OKhttp的一個程式碼段,當然伺服器沒開,這裡就有一點需要注意的了,有時候網路請求需要很長時間才結束,在實際開發中肯定是不行,然後你可以再加一個計時器在裡面,當請求時間過長時,直接停止重新整理提示網路超時。

還有一個大問題,那就是當recyclerview是表格佈局的時候,加的那個footer也是表格的一格,這樣顯得非常難看。所以要重寫Adapter裡這個函式onAttachedToRecyclerView 

根據文件解釋,在recyclerview observe這個介面卡的時候呼叫這個方法。通過這個方法判斷網格佈局的格數然後給footer設定佔幾個item。但是要注意,在你的主活動中必須將setadapter放在setlayout後面,因為這個方法裡面做了網格佈局的判斷,設定了網格佈局才有效。程式碼如下

 //setadapter必須放在setlayout的後面,不然不會呼叫到這個方法
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {

                    // 如果當前是footer的位置,那麼該item佔據2個單元格,正常情況下佔據1個單元格
                    return getItemViewType(position) == FOOT ? gridManager.getSpanCount() : 1;
                }
            });
        }
    }

最後說幾點應當注意的地方。上拉或者下拉正在重新整理的時候應該不然它觸發新的重新整理,雖然這種情況很少,但是也要避免。網路請求的時候,時間是不可控的,有時候時間過長影響體驗,所以應該在設一個計時器,重新整理達到一定時間據強制關閉重新整理並提醒(我的程式碼中沒寫這一段的程式碼)。當上拉的時候底部的footer應該要有不讓它顯示出來,這樣有拖動效果,而這一部分割槽域正好用來被上拉重新整理替換。然後重新整理完後,footer還在,所以要上滑一段距離。最後再給一張改進過的效果圖