1. 程式人生 > >RecycleView實現橫向帶指示器翻頁滑動,一行兩列自定義佈局

RecycleView實現橫向帶指示器翻頁滑動,一行兩列自定義佈局

首先看需求效果
這裡寫圖片描述
在acvitity中有一個控制元件,需要實現這種分頁效果,還要指示器,並且每頁的兩列不能太分散,使用GridView就很不好實現,這裡用RecycleView展示,先看成型後需要用到的結構(需要用到哪些自定義的東西)
這裡寫圖片描述
自己在Java下建一個新的包,需要這三個自定義的東西,一個用來展示GridView佈局樣式自定義的GridViewLayoutManger,一個指示器的自定義控制元件,第三個就是自定義RecleView,實現翻頁效果,另外還有一個工具類,實現獲取螢幕尺寸,並且讓px和dp相互轉換的功能,網上有很多,沒有幾行程式碼,之前也專門寫過,不再贅述,接下來就看著三個自定義東西怎麼寫

1、AutoGridLayoutManager

package inditor.recycleview.horizontal;

import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by YTF on 2017/9/16.
 */

public
class AutoGridLayoutManager extends GridLayoutManager { private int measuredWidth = 0; private int measuredHeight = 0; public AutoGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public
AutoGridLayoutManager(Context context, int spanCount) { super(context, spanCount); } public AutoGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { super(context, spanCount, orientation, reverseLayout); } @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { if (measuredHeight <= 0) { int count = state.getItemCount(); if(count>0){ View view = recycler.getViewForPosition(0); if (view != null) { measureChild(view, widthSpec, heightSpec); measuredWidth = View.MeasureSpec.getSize(widthSpec); measuredHeight = view.getMeasuredHeight() * getSpanCount(); } } } setMeasuredDimension(measuredWidth, measuredHeight); } }

2、PageIndicatorView

package inditor.recycleview.horizontal;

import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;

import com.lab.web.entity.UtilDynamicWidth;

import java.util.ArrayList;
import java.util.List;


/**
 * Created by shichaohui on 2015/7/10 0010.
 * <p/>
 * 頁碼指示器類,獲得此類例項後,可通過{@link PageIndicatorView#initIndicator(int)}方法初始化指示器
 * </P>
 */

public class PageIndicatorView extends LinearLayout {

    private Context mContext = null;
    private int dotSize = 10; // 指示器的大小(dp)
    private int margins = 10; // 指示器間距(dp)
    private List<View> indicatorViews = null; // 存放指示器

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

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

    public PageIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        this.mContext = context;

        setGravity(Gravity.CENTER);
        setOrientation(HORIZONTAL);

        dotSize = UtilDynamicWidth.dip2px(context, dotSize);
        margins = UtilDynamicWidth.dip2px(context, margins);
    }

    /**
     * 初始化指示器,預設選中第一頁
     *
     * @param count 指示器數量,即頁數
     */
    public void initIndicator(int count) {

        if (indicatorViews == null) {
            indicatorViews = new ArrayList<>();
        } else {
            indicatorViews.clear();
            removeAllViews();
        }
        View view;
        LayoutParams params = new LayoutParams(dotSize, dotSize);
        params.setMargins(margins, margins, margins, margins);
        for (int i = 0; i < count; i++) {
            view = new View(mContext);
            view.setBackgroundResource(android.R.drawable.presence_invisible);
            addView(view, params);
            indicatorViews.add(view);
        }
        if (indicatorViews.size() > 0) {
            indicatorViews.get(0).setBackgroundResource(android.R.drawable.presence_online);
        }
    }

    /**
     * 設定選中頁
     *
     * @param selected 頁下標,從0開始
     */
    public void setSelectedPage(int selected) {
        for (int i = 0; i < indicatorViews.size(); i++) {
            if (i == selected) {
                indicatorViews.get(i).setBackgroundResource(android.R.drawable.presence_online);
            } else {
                indicatorViews.get(i).setBackgroundResource(android.R.drawable.presence_invisible);
            }
        }
    }

}

3、PageRecyclerView

package inditor.recycleview.horizontal;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;

import com.lab.web.entity.UtilDynamicWidth;

import java.util.List;
import java.util.Map;

/**
 * Created by shichaohui on 2015/7/9 0009.
 * <p>
 * 橫向分頁的GridView效果
 * </p>
 * <p>
 * 預設為1行,每頁3列,如果要自定義行數和列數,請在呼叫{@link PageRecyclerView#setAdapter(Adapter)}方法前呼叫
 * {@link PageRecyclerView#setPageSize(int, int)}方法自定義行數
 * </p>
 */
public class PageRecyclerView extends RecyclerView {

    private Context mContext = null;

    private PageAdapter myAdapter = null;

    private int shortestDistance; // 超過此距離的滑動才有效
    private float slideDistance = 0; // 滑動的距離
    private float scrollX = 0; // X軸當前的位置

    private int spanRow = 1; // 行數
    private int spanColumn = 2; // 每頁列數
    private int totalPage = 0; // 總頁數
    private int currentPage = 1; // 當前頁

    private int pageMargin = 0; // 頁間距

    private PageIndicatorView mIndicatorView = null; // 指示器佈局

    private int realPosition = 0; // 真正的位置(從上到下從左到右的排列方式變換成從左到右從上到下的排列方式後的位置)

    /*
     * 0: 停止滾動且手指移開; 1: 開始滾動; 2: 手指做了拋的動作(手指離開螢幕前,用力滑了一下)
     */
    private int scrollState = 0; // 滾動狀態

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

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

    public PageRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        defaultInit(context);
    }

    // 預設初始化
    private void defaultInit(Context context) {
        this.mContext = context;
        setLayoutManager(new AutoGridLayoutManager(
                mContext, spanRow, AutoGridLayoutManager.HORIZONTAL, false));
        setOverScrollMode(OVER_SCROLL_NEVER);
    }

    /**
     * 設定行數和每頁列數
     *
     * @param spanRow    行數,<=0表示使用預設的行數
     * @param spanColumn 每頁列數,<=0表示使用預設每頁列數
     */
    public void setPageSize(int spanRow, int spanColumn) {
        this.spanRow = spanRow <= 0 ? this.spanRow : spanRow;
        this.spanColumn = spanColumn <= 0 ? this.spanColumn : spanColumn;
        setLayoutManager(new AutoGridLayoutManager(
                mContext, this.spanRow, AutoGridLayoutManager.HORIZONTAL, false));
    }

    /**
     * 設定頁間距
     *
     * @param pageMargin 間距(px)
     */
    public void setPageMargin(int pageMargin) {
        this.pageMargin = pageMargin;
    }

    /**
     * 設定指示器
     *
     * @param indicatorView 指示器佈局
     */
    public void setIndicator(PageIndicatorView indicatorView) {
        this.mIndicatorView = indicatorView;
    }

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        super.onMeasure(widthSpec, heightSpec);
        shortestDistance = getMeasuredWidth() / 3;
    }

    @Override
    public void setAdapter(Adapter adapter) {
        super.setAdapter(adapter);
        this.myAdapter = (PageAdapter) adapter;
        update();
    }

    // 更新頁碼指示器和相關資料
    private void update() {
        // 計算總頁數
        int temp = ((int) Math.ceil(myAdapter.dataList.size() / (double) (spanRow * spanColumn)));
        if (temp != totalPage) {
            mIndicatorView.initIndicator(temp);
            // 頁碼減少且當前頁為最後一頁
            if (temp < totalPage && currentPage == totalPage) {
                currentPage = temp;
                // 執行滾動
                smoothScrollBy(-getWidth(), 0);
            }
            mIndicatorView.setSelectedPage(currentPage - 1);
            totalPage = temp;
        }
    }

    @Override
    public void onScrollStateChanged(int state) {
        switch (state) {
            case 2:
                scrollState = 2;
                break;
            case 1:
                scrollState = 1;
                break;
            case 0:
                if (slideDistance == 0) {
                    break;
                }
                scrollState = 0;
                if (slideDistance < 0) { // 上頁
                    currentPage = (int) Math.ceil(scrollX / getWidth());
                    if (currentPage * getWidth() - scrollX < shortestDistance) {
                        currentPage += 1;
                    }
                } else { // 下頁
                    currentPage = (int) Math.ceil(scrollX / getWidth()) + 1;
                    if (currentPage <= totalPage) {
                        if (scrollX - (currentPage - 2) * getWidth() < shortestDistance) {
                            // 如果這一頁滑出距離不足,則定位到前一頁
                            currentPage -= 1;
                        }
                    } else {
                        currentPage = totalPage;
                    }
                }
                // 執行自動滾動
                smoothScrollBy((int) ((currentPage - 1) * getWidth() - scrollX), 0);
                // 修改指示器選中項
                mIndicatorView.setSelectedPage(currentPage - 1);
                slideDistance = 0;
                break;
        }
        super.onScrollStateChanged(state);
    }

    @Override
    public void onScrolled(int dx, int dy) {
        scrollX += dx;
        if (scrollState == 1) {
            slideDistance += dx;
        }

        super.onScrolled(dx, dy);
    }

    /**
     * 資料介面卡
     */
    public class PageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

        private List<Map<String, Object>> dataList = null;
        private CallBack mCallBack = null;
        private int itemWidth = 0;
        private int itemCount = 0;

        /**
         * 例項化介面卡
         *
         * @param data
         * @param callBack
         */
        public PageAdapter(List<Map<String, Object>> data, CallBack callBack) {
            this.dataList = data;
            this.mCallBack = callBack;
            itemCount = dataList.size(); //+ spanRow * spanColumn;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (itemWidth <= 0) {
                // 計算Item的寬度
//                parent.post(new Runnable() {
//                    @Override
//                    public void run() {
//                        itemWidth = (getWidth() - pageMargin * 2) / spanColumn;
//                    }
//                });
//                itemWidth = (parent.getWidth() - pageMargin * 2) / spanColumn;
                //獲取手機螢幕寬度px
                WindowManager wm = (WindowManager) mContext
                        .getSystemService(Context.WINDOW_SERVICE);
                DisplayMetrics outMetrics = new DisplayMetrics();
                wm.getDefaultDisplay().getMetrics(outMetrics);
                int withScreen=outMetrics.widthPixels;
                itemWidth = withScreen/6;
            }

            RecyclerView.ViewHolder holder = mCallBack.onCreateViewHolder(parent, viewType);

            holder.itemView.measure(0, 0);
            holder.itemView.getLayoutParams().width = itemWidth;
            holder.itemView.getLayoutParams().height = holder.itemView.getMeasuredHeight();

            return holder;
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {

            WindowManager wm = (WindowManager) mContext
                    .getSystemService(Context.WINDOW_SERVICE);
            DisplayMetrics outMetrics = new DisplayMetrics();
            wm.getDefaultDisplay().getMetrics(outMetrics);
            int withScreen=outMetrics.widthPixels;
            if (spanColumn == -1) {
                // 每個Item距離左右兩側各pageMargin
//                holder.itemView.getLayoutParams().width = itemWidth + pageMargin * 2;
//                holder.itemView.setPadding(pageMargin, 0, pageMargin, 0);
            } else {

                int m = position % (spanRow * spanColumn);
                if (m < spanRow) {
                    // 每頁左側的Item距離左邊pageMargin
//                    holder.itemView.getLayoutParams().width = itemWidth + pageMargin;
//                    mContext.getWindowManager().getDefaultDisplay().getWidth();//獲取手機螢幕寬度px

                    holder.itemView.getLayoutParams().width =  withScreen/2;
                    holder.itemView.setPadding(pageMargin*7, 0, 0, 0);
                } else if (m >= spanRow * spanColumn - spanRow) {
                    // 每頁右側的Item距離右邊pageMargin
//                    holder.itemView.getLayoutParams().width = itemWidth + pageMargin;
                    holder.itemView.getLayoutParams().width =  withScreen/2;
                    holder.itemView.setPadding(0, 0, pageMargin*7, 0);
                } else {
                    // 中間的正常顯示
//                    holder.itemView.getLayoutParams().width = itemWidth;
//                    holder.itemView.setPadding(0, 0, 0, 0);
                }
            }

            countRealPosition(position);

            holder.itemView.setTag(realPosition);

            setListener(holder);
            mCallBack.onBindViewHolder(holder, position);
//            if (realPosition < dataList.size()) {
//                holder.itemView.setVisibility(View.VISIBLE);
//                mCallBack.onBindViewHolder(holder, realPosition);
//            } else {
//                holder.itemView.setVisibility(View.INVISIBLE);
//            }

        }

        @Override
        public int getItemCount() {
            return itemCount;
        }

        private void countRealPosition(int position) {
            // 為了使Item從左到右從上到下排列,需要position的值
//            int m = position % (spanRow * spanColumn);
            realPosition = position;
//            switch (m) {
//
//                case 1:
//                case 5:
//                    realPosition = position + 2;
//                    break;
//                case 3:
//                case 7:
//                    realPosition = position - 2;
//                    break;
//                case 2:
//                    realPosition = position + 4;
//                    break;
//                case 6:
//                    realPosition = position - 4;
//                    break;
//                case 0:
//                case 4:
//                case 8:
//                    realPosition = position;
//                    break;
//            }
        }

        private void setListener(ViewHolder holder) {
            // 設定監聽
            holder.itemView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mCallBack.onItemClickListener(v, (Integer) v.getTag());
                }
            });

            holder.itemView.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    mCallBack.onItemLongClickListener(v, (Integer) v.getTag());
                    return true;
                }
            });
        }

        /**
         * 刪除Item
         *
         * @param position 位置
         */
        public void remove(int position) {
            if (position < dataList.size()) {
                // 刪除資料
                dataList.remove(position);
                itemCount--;
                // 刪除Item
                notifyItemRemoved(position);
                // 更新介面上發生改變的Item
                notifyItemRangeChanged((currentPage - 1) * spanRow * spanColumn, currentPage * spanRow * spanColumn);
                // 更新頁碼指示器
                update();
            }
        }

    }

    public interface CallBack {

        /**
         * 建立VieHolder
         *
         * @param parent
         * @param viewType
         */
        RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);

        /**
         * 繫結資料到ViewHolder
         *
         * @param holder
         * @param position
         */
        void onBindViewHolder(RecyclerView.ViewHolder holder, int position);

        void onItemClickListener(View view, int position);

        void onItemLongClickListener(View view, int position);

    }
}

4、使用

寫完了上邊的123,就是相當於實現了一整個自定義的控制元件,這個控制元件可以實現橫向滑動翻頁,可以設定行數和列數,並且可以自定義設定指定兩個item之間的距離,避免一頁中顯示連個item顯示間距太大的問題,並且翻頁的同時會顯示指示器
4-1:xml檔案使用控制元件:

   <!--自定義RecycleView加指示器橫向滑動佈局-->
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="85dp">
                <inditor.recycleview.horizontal.PageRecyclerView
                    android:id="@+id/cusom_swipe_view"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                </inditor.recycleview.horizontal.PageRecyclerView>

                <inditor.recycleview.horizontal.PageIndicatorView
                    android:id="@+id/indicator"
                    android:layout_width="match_parent"
                    android:layout_height="12dp"
                    android:layout_alignParentBottom="true"
                    android:layout_marginBottom="3dp"></inditor.recycleview.horizontal.PageIndicatorView>
            </RelativeLayout>

4-2 java 程式碼繫結資料並展示,新增item的點選和長按事件

      mRecyclerView = (PageRecyclerView) findViewById(R.id.cusom_swipe_view);
                                // 設定指示器
                                mRecyclerView.setIndicator((PageIndicatorView) findViewById(R.id.indicator));
                                // 設定行數和列數
                                mRecyclerView.setPageSize(1, 2);

                                // 設定頁間距
                                mRecyclerView.setPageMargin(30);
                                // 設定資料
                                mRecyclerView.setAdapter(myAdapter=mRecyclerView.new PageAdapter(list_tequan1, new PageRecyclerView.CallBack() {
                                    @Override
                                    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                                        View view = LayoutInflater.from(HuiYuanCenterActivity.this).inflate(R.layout.tequan_item, parent, false);
                                        return new MyHolder(view);
                                    }

                                    @Override
                                    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
                                        Map<String,Object> map= (Map<String, Object>) list_tequan1.get(position);
                                        Glide.with(HuiYuanCenterActivity.this).load(map.get("img").toString()).into( ((MyHolder) holder).iv_huanyuan_zuo);
                                        ((MyHolder) holder).gv_text.setText((String) map.get("txt"));
                                    }

                                    @Override
                                    public void onItemClickListener(View view, int position) {
//                                        Toast.makeText(HuiYuanCenterActivity.this, "點選:"
//                                                + list_tequan1.get(position), Toast.LENGTH_SHORT).show();

                                    }

                                    @Override
                                    public void onItemLongClickListener(View view, int position) {
//                                        Toast.makeText(HuiYuanCenterActivity.this, "刪除:"
//                                                + list_tequan1.get(position), Toast.LENGTH_SHORT).show();
//                                        myAdapter.remove(position);
                                    }
                                }));