Android仿淘寶淘搶購(gòu)時(shí)間Tab的選中

目前項(xiàng)目中有個(gè)功能類似淘寶的淘搶購(gòu)界面,橫向的recyclerview,滾動(dòng)后始終固定中間位置,點(diǎn)擊后也固定到中間位置。首先先感謝一下提供思路的這位朋友,Android橫向滑動(dòng)自動(dòng)選中控件。

下面看一下效果。
QQ圖片20171108133011.png

一、自定義橫向滑動(dòng)的Recyclerview

public class AutoLocateHorizontalView extends RecyclerView {
    /**
     * 一個(gè)屏幕中顯示多少個(gè)item,必須為奇數(shù)
     */
    private int itemCount = 5;
    /**
     * 初始時(shí)選中的位置
     */
    private int initPos = 0;

    private int deltaX;
    private WrapperAdapter wrapAdapter;
    private Adapter adapter;
    private LinearLayoutManager linearLayoutManager;
    private boolean isInit;
    private OnSelectedPositionChangedListener listener;
    private boolean isFirstPosChanged = true;        //剛初始化時(shí)是否觸發(fā)位置改變的監(jiān)聽
    private int oldSelectedPos = initPos;   //記錄上次選中的位置
    /**
     * 當(dāng)前被選中的位置
     */
    private int selectPos = initPos;

    private Scroller mScroller;

    /**
     * 當(dāng)要調(diào)用moveToPosition()方法時(shí)要先記錄已經(jīng)移動(dòng)了多少位置
     */
    private int oldMoveX;

    private boolean isMoveFinished = true;

    public AutoLocateHorizontalView(Context context) {
        super(context);
    }

    public AutoLocateHorizontalView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public AutoLocateHorizontalView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    private void init() {
        mScroller = new Scroller(getContext());
        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (isInit) {
                    if (initPos >= adapter.getItemCount()) {
                        initPos = adapter.getItemCount() - 1;
                    }
                    if (isFirstPosChanged && listener != null) {
                        listener.selectedPositionChanged(initPos);
                    }
                    linearLayoutManager.scrollToPositionWithOffset(0, -initPos * (wrapAdapter.getItemWidth()));
                    isInit = false;
                }
            }
        });
    }

    /**
     * 設(shè)置初始化時(shí)選中的位置,該方法必須在{@link AutoLocateHorizontalView#setAdapter(Adapter) }之前調(diào)用
     *
     * @param initPos 初始位置,如果位置超過了item的數(shù)量則默認(rèn)選中最后一項(xiàng)item
     */
    public void setInitPos(int initPos) {
        if (adapter != null) {
            throw new RuntimeException("This method should be called before setAdapter()!");
        }
        this.initPos = initPos;
        selectPos = initPos;
        oldSelectedPos = initPos;
    }

    /**
     * 設(shè)置每次顯示多少個(gè)item,該方法必須在{@link AutoLocateHorizontalView#setAdapter(Adapter) }之前調(diào)用
     *
     * @param itemCount 必須為奇數(shù),否則默認(rèn)會(huì)設(shè)置成小于它的最大奇數(shù)
     */
    public void setItemCount(int itemCount) {
        if (adapter != null) {
            throw new RuntimeException("This method should be called before setAdapter()!");
        }
        if (itemCount % 2 == 0) {
            this.itemCount = itemCount - 1;
        } else {
            this.itemCount = itemCount;
        }
    }

    /**
     * 刪除item后偏移距離可能需要重新計(jì)算,從而保證selectPos的正確
     *
     * @param adapter
     */
    private void correctDeltax(Adapter adapter) {
        if (adapter.getItemCount() <= selectPos) {
            deltaX -= wrapAdapter.getItemWidth() * (selectPos - adapter.getItemCount() + 1);
        }
        calculateSelectedPos();
    }

    /**
     * 刪除時(shí)選中的數(shù)據(jù)發(fā)生改變,要重新回調(diào)方法
     *
     * @param startPos
     */
    private void reCallListenerWhenRemove(int startPos) {
        if (startPos <= selectPos && listener != null) {
            correctDeltax(adapter);
            listener.selectedPositionChanged(selectPos);
        } else {
            correctDeltax(adapter);
        }
    }

    /**
     * 添加數(shù)據(jù)時(shí)選中的數(shù)據(jù)發(fā)生改變,要重新回調(diào)方法
     *
     * @param startPos
     */
    private void reCallListenerWhenAdd(int startPos) {
        if (startPos <= selectPos && listener != null) {
            listener.selectedPositionChanged(selectPos);
        }
    }

    /**
     * 當(dāng)使用整體刷新時(shí)要重新回調(diào)方法
     */
    private void reCallListenerWhenChanged() {
        if (listener != null) {
            listener.selectedPositionChanged(selectPos);
        }
    }

    @Override
    public void setAdapter(final Adapter adapter) {
        this.adapter = adapter;
        this.wrapAdapter = new WrapperAdapter(adapter, getContext(), itemCount);
        adapter.registerAdapterDataObserver(new AdapterDataObserver() {

            @Override
            public void onChanged() {
                super.onChanged();
                wrapAdapter.notifyDataSetChanged();
                reCallListenerWhenChanged();
            }

            @Override
            public void onItemRangeInserted(int positionStart, int itemCount) {
                wrapAdapter.notifyDataSetChanged();
                reCallListenerWhenAdd(positionStart);
            }

            @Override
            public void onItemRangeRemoved(int positionStart, int itemCount) {
                wrapAdapter.notifyDataSetChanged();
                reCallListenerWhenRemove(positionStart);
            }
        });
        deltaX = 0;
        if (linearLayoutManager == null) {
            linearLayoutManager = new LinearLayoutManager(getContext());
        }
        linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        super.setLayoutManager(linearLayoutManager);
        super.setAdapter(this.wrapAdapter);
        isInit = true;
    }

    @Override
    public void setLayoutManager(LayoutManager layout) {
        if (!(layout instanceof LinearLayoutManager)) {
            throw new IllegalStateException("The LayoutManager here must be LinearLayoutManager!");
        }
        this.linearLayoutManager = (LinearLayoutManager) layout;
    }

    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);

        if (state == SCROLL_STATE_IDLE) {
            if (wrapAdapter == null) {
                return;
            }
            int itemWidth = wrapAdapter.getItemWidth();
            int headerFooterWidth = wrapAdapter.getHeaderFooterWidth();
            if (itemWidth == 0 || headerFooterWidth == 0) {
                //此時(shí)adapter還沒有準(zhǔn)備好,忽略此次調(diào)用
                return;
            }
            //超出上個(gè)item的位置
            int overLastPosOffset = deltaX % itemWidth;
            if (overLastPosOffset == 0) {
                //剛好處于一個(gè)item選中位置,無需滑動(dòng)偏移糾正
            } else if (Math.abs(overLastPosOffset) <= itemWidth / 2) {
                scrollBy(-overLastPosOffset, 0);
            } else if (overLastPosOffset > 0) {
                scrollBy((itemWidth - overLastPosOffset), 0);
            } else {
                scrollBy(-(itemWidth + overLastPosOffset), 0);
            }
            calculateSelectedPos();
            //此處通知刷新是為了重新繪制之前被選中的位置以及剛剛被選中的位置
            wrapAdapter.notifyItemChanged(oldSelectedPos + 1);
            wrapAdapter.notifyItemChanged(selectPos + 1);
            oldSelectedPos = selectPos;
            if (listener != null) {
                listener.selectedPositionChanged(selectPos);
            }
        }


    }

    public void moveToPosition(int position) {
        if(position < 0 || position > adapter.getItemCount() - 1){
            throw new IllegalArgumentException("Your position should be from 0 to "+(adapter.getItemCount()-1));
        }
        oldMoveX = 0;
        isMoveFinished = false;
        int itemWidth = wrapAdapter.getItemWidth();
        if (position != selectPos) {
            int deltx = (position - selectPos) * itemWidth;
            mScroller.startScroll(getScrollX(), getScrollY(), deltx, 0);
            postInvalidate();
        }
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {
            int x = mScroller.getCurrX() - oldMoveX;
            oldMoveX += x;
            scrollBy(x, 0);
        } else if (mScroller.isFinished()) {
            //此處通知刷新是為了重新繪制之前被選中的位置以及剛剛被選中的位置
            if (isMoveFinished) {
                return;
            }
            wrapAdapter.notifyItemChanged(oldSelectedPos + 1);
            wrapAdapter.notifyItemChanged(selectPos + 1);
            oldSelectedPos = selectPos;
            if (listener != null) {
                listener.selectedPositionChanged(selectPos);
            }
            isMoveFinished = true;
        }
    }

    @Override
    public void onScrolled(int dx, int dy) {
        super.onScrolled(dx, dy);
        deltaX += dx;
        calculateSelectedPos();
    }

    private void calculateSelectedPos() {
        int itemWidth = wrapAdapter.getItemWidth();
        if (deltaX > 0) {
            if(itemWidth==0){
                return;
            }
            selectPos = (deltaX) / itemWidth + initPos;
        } else {
            if(itemWidth==0){
                return;
            }
            selectPos = initPos + (deltaX) / itemWidth;
        }
    }

    class WrapperAdapter extends Adapter {
        private Context context;
        private Adapter adapter;
        private int itemCount;
        private static final int HEADER_FOOTER_TYPE = -1;
        private View itemView;
        /**
         * 頭部或尾部的寬度
         */
        private int headerFooterWidth;

        /**
         * 每個(gè)item的寬度
         */
        private int itemWidth;

        public WrapperAdapter(Adapter adapter, Context context, int itemCount) {
            this.adapter = adapter;
            this.context = context;
            this.itemCount = itemCount;
            if (adapter instanceof IAutoLocateHorizontalView) {
                itemView = ((IAutoLocateHorizontalView) adapter).getItemView();
            } else {
                throw new RuntimeException(adapter.getClass().getSimpleName() + " should implements com.jianglei.view.AutoLocateHorizontalView.IAutoLocateHorizontalView !");
            }
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == HEADER_FOOTER_TYPE) {
                View view = new View(context);
                headerFooterWidth = parent.getMeasuredWidth() / 2 - (parent.getMeasuredWidth() / itemCount) / 2;
                LayoutParams params = new LayoutParams(headerFooterWidth, ViewGroup.LayoutParams.MATCH_PARENT);
                view.setLayoutParams(params);
                return new HeaderFooterViewHolder(view);
            }
            ViewHolder holder = adapter.onCreateViewHolder(parent, viewType);
            itemView = ((IAutoLocateHorizontalView) adapter).getItemView();
            int width = parent.getMeasuredWidth() / itemCount;
            ViewGroup.LayoutParams params = itemView.getLayoutParams();
            if (params != null) {
                params.width = width;
                itemWidth = width;
                itemView.setLayoutParams(params);
            }
            return holder;
        }

        @SuppressWarnings("unchecked")
        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            if (!isHeaderOrFooter(position)) {
                adapter.onBindViewHolder(holder, position - 1);
                if (selectPos == position - 1) {
                    ((IAutoLocateHorizontalView) adapter).onViewSelected(true, position - 1, holder, itemWidth);
                } else {
                    ((IAutoLocateHorizontalView) adapter).onViewSelected(false, position - 1, holder, itemWidth);
                }
            }
        }


        @Override
        public int getItemCount() {
            return adapter.getItemCount() + 2;
        }

        @Override
        public int getItemViewType(int position) {
            if (position == 0 || position == getItemCount() - 1) {
                return HEADER_FOOTER_TYPE;
            }
            return adapter.getItemViewType(position - 1);
        }


        private boolean isHeaderOrFooter(int pos) {
            if (pos == 0 || pos == getItemCount() - 1) {
                return true;
            }
            return false;
        }

        public int getHeaderFooterWidth() {
            return headerFooterWidth;
        }

        public int getItemWidth() {
            return itemWidth;
        }

        class HeaderFooterViewHolder extends ViewHolder {

            HeaderFooterViewHolder(View itemView) {
                super(itemView);
            }
        }


    }


    public interface IAutoLocateHorizontalView {
        /**
         * 獲取item的根布局
         */
        View getItemView();

        /**
         * 當(dāng)item被選中時(shí)會(huì)觸發(fā)這個(gè)回調(diào),可以修改被選中時(shí)的樣式
         *
         * @param isSelected 是否被選中
         * @param pos        當(dāng)前view的位置
         * @param holder
         * @param itemWidth  當(dāng)前整個(gè)item的寬度
         */
        void onViewSelected(boolean isSelected, int pos, ViewHolder holder, int itemWidth);
    }

    /***
     * 選中位置改變時(shí)的監(jiān)聽
     */
    public interface OnSelectedPositionChangedListener {
        void selectedPositionChanged(int pos);
    }

    public void setOnSelectedPositionChangedListener(OnSelectedPositionChangedListener listener) {
        this.listener = listener;
    }
}

二、其他使用步驟和RecyclerView基本一致

1、初始化RecyclerView
2、實(shí)例化適配器
3、添加數(shù)據(jù)

recyclerview = ((AutoLocateHorizontalView) findViewById(R.id.recyclerViewId));
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerview.setHasFixedSize(true);
        recyclerview.setLayoutManager(linearLayoutManager);
        recyclerview.setOnSelectedPositionChangedListener(new AutoLocateHorizontalView.OnSelectedPositionChangedListener() {
            @Override
            public void selectedPositionChanged(int pos) {
                Log.i("===位置位置====","fragment:滾動(dòng)滾動(dòng):"+pos);
                contentTv.setText("當(dāng)前位置下標(biāo):"+pos);

            }
        });
        testAdapter = new TestAdapter(new ArrayList<TestEntity>(), this, new TestAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(int position, View view) {
                Log.i("===位置位置====","fragment:點(diǎn)擊點(diǎn)擊:"+position);
                recyclerview.moveToPosition(position-1);
            }
        });

        recyclerview.setInitPos(5);
        recyclerview.setItemCount(5);
        recyclerview.setAdapter(testAdapter);

模擬添加一些數(shù)據(jù)

List<String> list = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        list.add("20:00");
        list.add("21:00");
        list.add("09:00");
        list.add("10:00");
        list.add("12:00");
        list.add("14:00");
        list.add("16:00");
        list.add("18:00");
        list.add("20:00");

        list2.add("昨日精選");
        list2.add("昨日精選");
        list2.add("昨日精選");
        list2.add("搶購(gòu)中");
        list2.add("搶購(gòu)中");
        list2.add("搶購(gòu)中");
        list2.add("搶購(gòu)中");
        list2.add("預(yù)熱中");
        list2.add("預(yù)熱中");
        List<TestEntity> list333 = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            TestEntity en = new TestEntity(list.get(i),list2.get(i));
            list333.add(en);
        }
        testAdapter.clear();
        testAdapter.addAll(list333);

想要查看具體的效果可以下載app查看。

微信圖片_20171108134640.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,781評(píng)論 25 709
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 47,134評(píng)論 22 665
  • 中秋節(jié),也稱拜月節(jié),團(tuán)圓節(jié)。 月圓人全,家家團(tuán)圓,想必是家庭最幸福的時(shí)刻。 然則月有陰晴圓缺,人有悲歡離合,送走奶...
    花香琳瑯閱讀 240評(píng)論 0 0

友情鏈接更多精彩內(nèi)容