Android仿淘寶淘搶購時間Tab的選中

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

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

一墓塌、自定義橫向滑動的Recyclerview

public class AutoLocateHorizontalView extends RecyclerView {
    /**
     * 一個屏幕中顯示多少個item瘟忱,必須為奇數(shù)
     */
    private int itemCount = 5;
    /**
     * 初始時選中的位置
     */
    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;        //剛初始化時是否觸發(fā)位置改變的監(jiān)聽
    private int oldSelectedPos = initPos;   //記錄上次選中的位置
    /**
     * 當(dāng)前被選中的位置
     */
    private int selectPos = initPos;

    private Scroller mScroller;

    /**
     * 當(dāng)要調(diào)用moveToPosition()方法時要先記錄已經(jī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è)置初始化時選中的位置,該方法必須在{@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è)置每次顯示多少個item,該方法必須在{@link AutoLocateHorizontalView#setAdapter(Adapter) }之前調(diào)用
     *
     * @param itemCount 必須為奇數(shù),否則默認(rèn)會設(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后偏移距離可能需要重新計算酷誓,從而保證selectPos的正確
     *
     * @param adapter
     */
    private void correctDeltax(Adapter adapter) {
        if (adapter.getItemCount() <= selectPos) {
            deltaX -= wrapAdapter.getItemWidth() * (selectPos - adapter.getItemCount() + 1);
        }
        calculateSelectedPos();
    }

    /**
     * 刪除時選中的數(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ù)據(jù)發(fā)生改變态坦,要重新回調(diào)方法
     *
     * @param startPos
     */
    private void reCallListenerWhenAdd(int startPos) {
        if (startPos <= selectPos && listener != null) {
            listener.selectedPositionChanged(selectPos);
        }
    }

    /**
     * 當(dāng)使用整體刷新時要重新回調(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) {
                //此時adapter還沒有準(zhǔn)備好盐数,忽略此次調(diào)用
                return;
            }
            //超出上個item的位置
            int overLastPosOffset = deltaX % itemWidth;
            if (overLastPosOffset == 0) {
                //剛好處于一個item選中位置,無需滑動偏移糾正
            } 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;

        /**
         * 每個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被選中時會觸發(fā)這個回調(diào)伞梯,可以修改被選中時的樣式
         *
         * @param isSelected 是否被選中
         * @param pos        當(dāng)前view的位置
         * @param holder
         * @param itemWidth  當(dāng)前整個item的寬度
         */
        void onViewSelected(boolean isSelected, int pos, ViewHolder holder, int itemWidth);
    }

    /***
     * 選中位置改變時的監(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:滾動滾動:"+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("搶購中");
        list2.add("搶購中");
        list2.add("搶購中");
        list2.add("搶購中");
        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)容合作請聯(lián)系作者
  • 序言:七十年代末峦阁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子趁矾,更是在濱河造成了極大的恐慌且预,老刑警劉巖槽袄,帶你破解...
    沈念sama閱讀 212,080評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锋谐,居然都是意外死亡遍尺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評論 3 385
  • 文/潘曉璐 我一進(jìn)店門涮拗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乾戏,“玉大人,你說我怎么就攤上這事三热」脑瘢” “怎么了?”我有些...
    開封第一講書人閱讀 157,630評論 0 348
  • 文/不壞的土叔 我叫張陵就漾,是天一觀的道長呐能。 經(jīng)常有香客問我,道長从藤,這世上最難降的妖魔是什么催跪? 我笑而不...
    開封第一講書人閱讀 56,554評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮夷野,結(jié)果婚禮上懊蒸,老公的妹妹穿的比我還像新娘。我一直安慰自己悯搔,他們只是感情好骑丸,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般通危。 火紅的嫁衣襯著肌膚如雪铸豁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,856評論 1 290
  • 那天菊碟,我揣著相機(jī)與錄音节芥,去河邊找鬼。 笑死逆害,一個胖子當(dāng)著我的面吹牛头镊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播魄幕,決...
    沈念sama閱讀 39,014評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼相艇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了纯陨?” 一聲冷哼從身側(cè)響起坛芽,我...
    開封第一講書人閱讀 37,752評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎翼抠,沒想到半個月后咙轩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,212評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡机久,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評論 2 327
  • 正文 我和宋清朗相戀三年臭墨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膘盖。...
    茶點(diǎn)故事閱讀 38,687評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡胧弛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出侠畔,到底是詐尸還是另有隱情结缚,我是刑警寧澤,帶...
    沈念sama閱讀 34,347評論 4 331
  • 正文 年R本政府宣布软棺,位于F島的核電站红竭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏喘落。R本人自食惡果不足惜茵宪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瘦棋。 院中可真熱鬧稀火,春花似錦、人聲如沸赌朋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赡若,卻和暖如春达布,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逾冬。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評論 1 266
  • 我被黑心中介騙來泰國打工黍聂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粉渠。 一個月前我還...
    沈念sama閱讀 46,406評論 2 360
  • 正文 我出身青樓分冈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親霸株。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評論 2 349

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,800評論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,731評論 22 665
  • 中秋節(jié)集乔,也稱拜月節(jié)去件,團(tuán)圓節(jié)扰路。 月圓人全尤溜,家家團(tuán)圓,想必是家庭最幸福的時刻汗唱。 然則月有陰晴圓缺宫莱,人有悲歡離合,送走奶...
    花香琳瑯閱讀 174評論 0 0