重寫(xiě)RecyclerView使其在TV端運(yùn)行

原生RecyclerView使用時(shí)的局限性

在手機(jī)端使用RecyclerView相信都很簡(jiǎn)單,但是在TV和盒子端就會(huì)出現(xiàn)不少的問(wèn)題抹剩,例如焦點(diǎn)顯示不全,無(wú)法定位到某個(gè)position等等

下面總結(jié)下在TV端使用使用RecyclerView時(shí)踩過(guò)的坑

(一) 解決RecyclerView刷新數(shù)據(jù)的時(shí)候纵装,焦點(diǎn)錯(cuò)亂問(wèn)題

下面是google官方給出的解決方案

1. adapter調(diào)用setHasStableIds(true)方法

2. 重寫(xiě)getItemId()方法,讓每個(gè)view都有各自的id

public abstract class BaseRecyclerAdapter<VH extends BaseRecyclerViewHolder, T> extends RecyclerView.Adapter<BaseRecyclerViewHolder> {

    public LayoutInflater mInflate;

    public List<T> mDataList;

    public BaseRecyclerAdapter(Context context, List<T> dataList)     {
        this.mInflate = LayoutInflater.from(context);
        this.mDataList = dataList;
        setHasStableIds(true);
    }

    public void setDataList(List<T> dataList) {
        this.mDataList = dataList;
    }

    public void addDataList(List<T> dataList) {
        if (dataList != null && dataList.size()>0) {
            this.mDataList.addAll(dataList);
        }
    }

    @Override
    public int getItemCount() {
        return mDataList != null ? mDataList.size() : 0;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }


    public List<T> getDataList() {
        return mDataList;
    }

    @Override
    public void onBindViewHolder(BaseRecyclerViewHolder holder, int position) {
        onBindBaseViewHolder((VH) holder, position);
    }

    public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);

    protected abstract void onBindBaseViewHolder(VH viewHolder, int position);

    /**
     * @param hasStableIds 有多個(gè)observer的話會(huì)報(bào)錯(cuò)
     */
    @Override
    public void setHasStableIds(boolean hasStableIds) {
        super.setHasStableIds(hasStableIds);
    }

}

3. RecyclerView的動(dòng)畫(huà)必須去掉

setItemAnimator(null);

(二)解決長(zhǎng)按遙控器叭爱,讓RecyclerView快速滑動(dòng)酵使,焦點(diǎn)錯(cuò)亂問(wèn)題

1. 由于長(zhǎng)按遙控器的時(shí)候盖桥,RecyclerView找不到findFocusView為null革屠,此時(shí)我就把RecyclerView的onKeyUp事件給屏蔽了

2. 通過(guò)FocusFind類找到view锚赤,并讓view請(qǐng)求焦點(diǎn)匹舞;

3.focusFind焦點(diǎn)失敗之后,如果是讓其滑動(dòng)item的寬度(左后滑動(dòng))或者高度(上下滑動(dòng))线脚,因?yàn)橛锌赡芑较旅孢€有可能有item赐稽,否則RecyclerView將不會(huì)繼續(xù)滑動(dòng)

 @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInterceptLister != null && mInterceptLister.onIntercept(event)) {
            return true;
        }


        boolean result = super.dispatchKeyEvent(event);
        View focusView = this.findFocus();
        if (focusView == null) {
            return result;
        } else {

            int dy = 0;
            int dx = 0;
            if (getChildCount() > 0) {
                View firstView = this.getChildAt(0);
                dy = firstView.getHeight();
                dx = firstView.getWidth();
            }
            if (event.getAction() == KeyEvent.ACTION_UP) {
                return true;
            } else {
                switch (event.getKeyCode()) {
                    case KeyEvent.KEYCODE_DPAD_RIGHT:
                        View rightView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT);
                        Log.i(TAG, "rightView is null:" + (rightView == null));
                        if (rightView != null) {
                            rightView.requestFocus();
                            return true;
                        } else {
                            this.smoothScrollBy(dx, 0);
                            return true;
                        }
                    case KeyEvent.KEYCODE_DPAD_LEFT:
                        View leftView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_LEFT);
                        Log.i(TAG, "leftView is null:" + (leftView == null));
                        if (leftView != null) {
                            leftView.requestFocus();
                            return true;
                        } else {
                            this.smoothScrollBy(-dx, 0);
                            return true;
                        }
                    case KeyEvent.KEYCODE_DPAD_DOWN:
                        View downView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_DOWN);
                        Log.i(TAG, " downView is null:" + (downView == null));
                        if (downView != null) {
                            downView.requestFocus();
                            return true;
                        } else {
                            this.smoothScrollBy(0, dy);
                            return true;
                        }
                    case KeyEvent.KEYCODE_DPAD_UP:
                        View upView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_UP);
                        Log.i(TAG, "upView is null:" + (upView == null));
                        if (event.getAction() == KeyEvent.ACTION_UP) {
                            return true;
                        } else {
                            if (upView != null) {
                                upView.requestFocus();
                                return true;
                            } else {
                                this.smoothScrollBy(0, -dy);
                                return true;
                            }

                        }
                }

            }

        }
        return result;
    }

(三)解決RecyclerView 定位到某個(gè)item不獲取焦點(diǎn)問(wèn)題

RecyclerView僅僅提供了一個(gè)smoothScrollToPosition(int position)方法叫榕,而且該方法,能滑到指定的position位置姊舵,但是該position處的item并沒(méi)有獲取焦點(diǎn)晰绎。

想起在使用LeanBack中的RecyclerView時(shí),你會(huì)發(fā)現(xiàn)它提供了專門(mén)定位到某個(gè)position括丁,并獲取焦點(diǎn)的方法荞下。但是然后沉下心把源碼給看了,終于明白了其中的原理史飞,原來(lái)它在定位到指定位置的item后尖昏,找到目標(biāo)的item,并讓其主動(dòng)請(qǐng)求焦點(diǎn)构资。完美解決

(1) RecyclerView的smoothScrollToPosition方法抽诉,最終執(zhí)行的是LayoutManger的smoothScrollToPosition方法。下面是RecyclerView的
smoothScrollToPosition的源代碼

  public void smoothScrollToPosition(int position) {
        if (mLayoutFrozen) {
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " +
                    "Call setLayoutManager with a non-null argument.");
            return;
        }
        mLayout.smoothScrollToPosition(this, mState, position);
    }

(2) RecyclerView彈性滑動(dòng)是通過(guò)SmoothScroller來(lái)實(shí)現(xiàn)的吐绵,SmoothScroller中有onStart和onStop的回調(diào)迹淌,大膽猜測(cè)一下,onStop肯定是滑動(dòng)結(jié)束的回調(diào)己单,于是找到targetView唉窃,并讓其請(qǐng)求焦點(diǎn),問(wèn)題得以解決

/**
 * Created by liuyu on 17/2/8.
 * fix issue: RecyclerView 進(jìn)行item定位的時(shí)候,item沒(méi)有獲取焦點(diǎn)
 */
public class TvGridLayoutManager extends GridLayoutManager {


    public TvGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public TvGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public TvGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }


    /**
     * Base class which scrolls to selected view in onStop().
     */
    abstract class GridLinearSmoothScroller extends LinearSmoothScroller {


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

        /**
         * 滑動(dòng)完成后,讓該targetPosition 處的item獲取焦點(diǎn)
         */
        @Override
        protected void onStop() {
            super.onStop();
            View targetView = findViewByPosition(getTargetPosition());

            if (targetView != null) {
                targetView.requestFocus();
            }
            super.onStop();
        }

    }

    /**
     * RecyclerView的smoothScrollToPosition方法最終會(huì)執(zhí)行smoothScrollToPosition
     * @param recyclerView
     * @param state
     * @param position
     */
    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
                                       int position) {
        GridLinearSmoothScroller linearSmoothScroller =
                new GridLinearSmoothScroller(recyclerView.getContext()) {
                    @Override
                    public PointF computeScrollVectorForPosition(int targetPosition) {
                        return computeVectorForPosition(targetPosition);
                    }
                };
        linearSmoothScroller.setTargetPosition(position);
        startSmoothScroll(linearSmoothScroller);
    }

    public PointF computeVectorForPosition(int targetPosition) {
        return super.computeScrollVectorForPosition(targetPosition);
    }
}

為了方便大家我把代碼上傳到了github:https://github.com/Fredlxy/TVRecyclerView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纹笼,一起剝皮案震驚了整個(gè)濱河市纹份,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌允乐,老刑警劉巖矮嫉,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件削咆,死亡現(xiàn)場(chǎng)離奇詭異牍疏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)拨齐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)鳞陨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人瞻惋,你說(shuō)我怎么就攤上這事厦滤。” “怎么了歼狼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵掏导,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我羽峰,道長(zhǎng)趟咆,這世上最難降的妖魔是什么添瓷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮值纱,結(jié)果婚禮上鳞贷,老公的妹妹穿的比我還像新娘。我一直安慰自己虐唠,他們只是感情好搀愧,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著疆偿,像睡著了一般咱筛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杆故,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天眷蚓,我揣著相機(jī)與錄音,去河邊找鬼反番。 笑死沙热,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的罢缸。 我是一名探鬼主播篙贸,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼枫疆!你這毒婦竟也來(lái)了爵川?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤息楔,失蹤者是張志新(化名)和其女友劉穎寝贡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體值依,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡圃泡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了愿险。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颇蜡。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖辆亏,靈堂內(nèi)的尸體忽然破棺而出风秤,到底是詐尸還是另有隱情,我是刑警寧澤扮叨,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布缤弦,位于F島的核電站,受9級(jí)特大地震影響彻磁,放射性物質(zhì)發(fā)生泄漏碍沐。R本人自食惡果不足惜惦费,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抢韭。 院中可真熱鬧薪贫,春花似錦、人聲如沸刻恭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鳍贾。三九已至鞍匾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骑科,已是汗流浹背橡淑。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咆爽,地道東北人梁棠。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像斗埂,于是被迫代替她去往敵國(guó)和親符糊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,513評(píng)論 25 707
  • 簡(jiǎn)介: 提供一個(gè)讓有限的窗口變成一個(gè)大數(shù)據(jù)集的靈活視圖呛凶。 術(shù)語(yǔ)表: Adapter:RecyclerView的子類...
    酷泡泡閱讀 5,140評(píng)論 0 16
  • 簡(jiǎn)介 RecyclerView在24.2.0版本中新增了SnapHelper這個(gè)輔助類男娄,用于輔助RecyclerV...
    辰之貓閱讀 154,094評(píng)論 65 617
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,708評(píng)論 22 664
  • 文/清歌 今天的天,格外的藍(lán)漾稀。 陽(yáng)光溫柔模闲,從我采下幾朵黃色的花兒開(kāi)始。 這些小小的花朵崭捍,并...
    c56cbcc80239閱讀 371評(píng)論 0 0