針對Android Tv的自定義RecyclerView

2248831048 8年前發布 | 17K 次閱讀 Android開發 移動開發 RecyclerView

前言:Android TV Launcher頁在RecyclerView出來之前大家用GridView去實現。TV開發有五向鍵的監聽,遙控器hover監聽,點擊事件等。用GridView去處理焦點是有一定挑戰性的,往往會出現不可預料焦點錯亂問題。這里封裝了一個針對TV的RecyclerView,很方便的處理了這些事件。

下面是效果圖:

封裝了RecyclerView實現了一下一些功能:

1.響應五向鍵,按下五向鍵的上下左右會跟著移動,并獲得焦點,在獲得焦點時會抬高

2.在鼠標hover在條目上時會獲得焦點。

3.添加了條目的點擊和長按事件。

4.添加了是否第一個可見條目和是否是最后一個可見條目的方法。

5.在item獲得焦點時和失去焦點時,這里有相應的回調方法。

下面分析一下一些關鍵的點:

1.鼠標滑動時避免跟著滑動,只響應五向鍵和左右箭頭

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //在recyclerView的move事件情況下,攔截調,只讓它響應五向鍵和左右箭頭移動
        LogUtil.i(this, "CustomRecycleView.dispatchTouchEvent.");
        return ev.getAction() == MotionEvent.ACTION_MOVE || super.dispatchTouchEvent(ev);
    }

2.使用StaggeredGridLayoutManager實現管理,如果使用GridLayoutManager會出現焦點的錯亂,當使用五向鍵左右移動時,會從上面轉移到下面。原因是GridLayoutManager會存在分組。

//設置布局管理器
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.HORIZONTAL);
mRecyclerView.setLayoutManager(layoutManager);

3.設置RecyclerView的item有焦點。按五向鍵,焦點會跟著一起移動

holder.itemView.setFocusable(true);

4,左右鍵,讓RecyclerView跟著一起滾動,并獲得焦點:

@Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        boolean result = super.dispatchKeyEvent(event);
        int dx = this.getChildAt(0).getWidth();
        View focusView = this.getFocusedChild();
        if (focusView != null) {
            switch (event.getKeyCode()) {
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    if (event.getAction() == KeyEvent.ACTION_UP) {
                        return true;
                    } else {
                        View rightView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT);
                        LogUtil.i(this, "rightView is null:" + (rightView == null));
                        if (rightView != null) {
                            rightView.requestFocusFromTouch();
                            return true;
                        } else {
                            this.smoothScrollBy(dx, 0);
                            return true;
                        }
                    }
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    View leftView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_LEFT);
                    LogUtil.i(this, "left is null:" + (leftView == null));
                    if (event.getAction() == KeyEvent.ACTION_UP) {
                        return true;
                    } else {
                        if (leftView != null) {
                            leftView.requestFocusFromTouch();
                            return true;
                        } else {
                            this.smoothScrollBy(-dx, 0);
                            return true;
                        }
                    }
            }
        }
        return result;
    }

這里請求獲取焦點的方法是:

rightView.requestFocusFromTouch();

TV的焦點的處理的邏輯比較復雜:

可以參考這篇文章: http://www.cnblogs.com/myzh/p/3664544.html

5.在holder里監聽到焦點變化時做一些處理:

holder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
                @Override
                public void onFocusChange(View v, boolean hasFocus) {
                    if (hasFocus) {
                        focusStatus(v);
                    } else {
                        normalStatus(v);
                    }
                }
            });
        /**
         * item獲得焦點時調用
         *
         * @param itemView view
         */
        private void focusStatus(View itemView) {
            if (itemView == null) {
                return;
            }
            if (Build.VERSION.SDK_INT >= 21) {
                //抬高Z軸
                ViewCompat.animate(itemView).scaleX(1.10f).scaleY(1.10f).translationZ(1).start();
            } else {
                ViewCompat.animate(itemView).scaleX(1.10f).scaleY(1.10f).start();
                ViewGroup parent = (ViewGroup) itemView.getParent();
                parent.requestLayout();
                parent.invalidate();
            }
            onItemFocus(itemView);
        }
        /**
         * 當item獲得焦點時處理
         *
         * @param itemView itemView
         */
        protected abstract void onItemFocus(View itemView);
        /**
         * item失去焦點時
         *
         * @param itemView item對應的View
         */
        private void normalStatus(View itemView) {
            if (itemView == null) {
                return;
            }
            if (Build.VERSION.SDK_INT >= 21) {
                ViewCompat.animate(itemView).scaleX(1.0f).scaleY(1.0f).translationZ(0).start();
            } else {
                ViewCompat.animate(itemView).scaleX(1.0f).scaleY(1.0f).start();
                ViewGroup parent = (ViewGroup) itemView.getParent();
                parent.requestLayout();
                parent.invalidate();
            }
            onItemGetNormal(itemView);
        }
        /**
         * 當條目失去焦點時調用
         *
         * @param itemView 條目對應的View
         */
        protected abstract void onItemGetNormal(View itemView);

這里抽象了兩個方法,當item獲得焦點和失去焦點時調用。獲得焦點時條目會抬高,這里是抬高了Z軸。

6.獲取在第一個和最后一個可見的條目,根據這些狀態去顯示和隱藏左右箭頭。

/**
     * 第一個條目是否可見
     *
     * @return 可見返回true,不可見返回false
     */
    public boolean isFirstItemVisible() {
        LayoutManager layoutManager = getLayoutManager();
        if (layoutManager instanceof StaggeredGridLayoutManager) {
            int[] firstVisibleItems = null;
            firstVisibleItems = ((StaggeredGridLayoutManager) layoutManager).
                    findFirstCompletelyVisibleItemPositions(firstVisibleItems);
            int position = firstVisibleItems[0];
            return position == 0;
        } else if (layoutManager instanceof LinearLayoutManager) {
            int position = ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
            return position == 0;
        }
        return false;
    }
    /**
     * 最后一個條目是否可見
     *
     * @param lineNum    行數
     * @param allItemNum item總數
     * @return 可見返回true,不可見返回false
     */
    public boolean isLastItemVisible(int lineNum, int allItemNum) {
        LayoutManager layoutManager = getLayoutManager();
        if (layoutManager instanceof StaggeredGridLayoutManager) {
            int[] lastVisibleItems = null;
            lastVisibleItems = ((StaggeredGridLayoutManager) layoutManager).findLastCompletelyVisibleItemPositions(lastVisibleItems);
            int position = lastVisibleItems[0];
            LogUtil.i(this, "lastVisiblePosition:" + position);
            boolean isVisible = position >= (allItemNum - lineNum);
            if (isVisible) {
                scrollBy(1, 0);
            }
            return isVisible;
        } else if (layoutManager instanceof LinearLayoutManager) {
            int position = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
            return position == allItemNum - 1;
        }
        return false;
    }

下面說一個坑,在處理最后一個條目時可見時,我發現拿到的數據并不是一種情況,當一共有三行時。

用下面的代碼來打出位置:

for (int i = 0; i < lastVisibleItems.length; i++) {
                LogUtil.i(this, "order:"+i +"----->last position:" + lastVisibleItems[i]);
}

有三種情況:

1.最后一列只有有一個時,打出的log是

01-06 02:40:51.868 4135-4135/com.songwenju.customtvrecyclerview I/swjCustomRecyclerView: order:0----->last position:12
01-06 02:40:51.869 4135-4135/com.songwenju.customtvrecyclerview I/swjCustomRecyclerView: order:1----->last position:10
01-06 02:40:51.869 4135-4135/com.songwenju.customtvrecyclerview I/swjCustomRecyclerView: order:2----->last position:11

2.當最后一列有兩個時:

01-06 02:41:54.285 6109-6109/com.songwenju.customtvrecyclerview I/swjCustomRecyclerView: order:0----->last position:12
01-06 02:41:54.286 6109-6109/com.songwenju.customtvrecyclerview I/swjCustomRecyclerView: order:1----->last position:13
01-06 02:41:54.286 6109-6109/com.songwenju.customtvrecyclerview I/swjCustomRecyclerView: order:2----->last position:11

3.當最后一行有三個時:

01-06 02:43:21.336 8818-8818/com.songwenju.customtvrecyclerview I/swjCustomRecyclerView: order:0----->last position:12
01-06 02:43:21.337 8818-8818/com.songwenju.customtvrecyclerview I/swjCustomRecyclerView: order:1----->last position:13
01-06 02:43:21.337 8818-8818/com.songwenju.customtvrecyclerview I/swjCustomRecyclerView: order:2----->last position:14

所以這里的處理是傳入行數:

boolean isVisible = position >= (allItemNum - lineNum);來判斷是否可見。

7.在Recycler滾動時候去處理箭頭的顯示狀態:

private class MyOnScrollListener extends RecyclerView.OnScrollListener {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            //在滾動的時候處理箭頭的狀態
            setLeftArrStatus();
            setRightArrStatus();
        }
    }

來自:http://www.jianshu.com/p/566bd6188f4d

 

 本文由用戶 2248831048 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!