1. 程式人生 > >AndroidTV開發(十一)Android Tv Launcher自定義RecyclerView

AndroidTV開發(十一)Android Tv Launcher自定義RecyclerView

前言

Android TV Launcher頁在RecyclerView出來之前大家用GridView去實現(本人的FocusView)。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的焦點的處理的邏輯比較複雜:

可以參考:

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();
    }
}

結束:

注意在使用該控制元件時,要設定RecyclerView的寬度是Item的整數倍,左右箭頭點選滑動的距離也要設定為RecyclerView寬度。

相關推薦

AndroidTV開發Android Tv Launcher定義RecyclerView

前言 Android TV Launcher頁在RecyclerView出來之前大家用GridView去實現(本人的FocusView)。TV開發有五向鍵的監聽,遙控器hover監聽,點選事件等。用GridView去處理焦點是有一定挑戰性的,往往會出現不可預料焦點錯亂

CoreThink開發首頁控制器判斷移動設備還是PC並做相應處理

iss htm meizu http nec window sam assign clas 在home模塊Index控制器添加判斷代碼 application\Home\Controller\IndexController.class.php <?php // +

Go語言開發、Go語言常用標準庫

lena unix doc 計算 cmd.run ner rem 信息 前綴 Go語言開發(十一)、Go語言常用標準庫一 一、log 1、log模塊簡介 Go語言中log模塊用於在程序中輸出日誌。log模塊提供了三類日誌輸出接口,Print、Fatal和Panic。Prin

Android學習—— Android介面卡

Android介面卡 安卓的介面卡在我看來是一個非常重要的知識點,面對形式相同但資料來源較多的情況時,介面卡是一個比較好的解決方法。資料介面卡是建立了資料來源與控制元件之間的適配關係,將資料來源轉換為控制元件能夠顯示的資料格式,從而將資料的來源與資料的顯示進行解耦,降低程式的耦合性。這篇文章就說一下如何實現

從零開始學習比特幣開發-建立錢包

比特幣使用者最關心除了交易之外就是地址、錢包、私鑰了,交易、地址、錢包、私鑰這些不同概念之間具有內在的聯絡,要了解交易必須先要了解地址、錢包、私鑰這幾個概念,從本章開始,我們開始學習這一部分內容。 建立錢包整體流程 前面我們提到 RPC 的概念,RPC 是 re

C#.net 地圖控制元件開發 地圖控制元件MapControl

地圖控制元件     地圖控制元件(MapControl)包含了地圖物件(Map),並在控制元件重繪時將繪圖的控制代碼傳遞給地圖物件,讓地圖物件可以繪製圖層集合。 地圖控制元件類         作用:主要用來繪製地圖。         類:提供地圖物件屬性,可以將使用者自

QT開發——專案實戰:截圖工具

我們繼續來寫小玩意,本來寫了一個記事本,但是很無奈,功能實在是太多了,細節也需要處理的很多,所以很到一半就沒寫了,這次我們來寫一個截圖工具,先來看下UI的實現 我們要實現的功能不多,但是經典 1.新建截圖(全屏) 2.儲存截圖 3.複製圖片到系統貼上板

區塊鏈開發以太坊賬戶管理

賬戶 賬戶在以太坊中發揮著中心作用。共有兩種賬戶型別:外部賬戶(EOAs)和合約賬戶。我們這裡重點講一下外部賬戶,以下會簡稱為賬戶。合約賬戶簡稱為合約, 在合約章節具體討論。把外部賬戶和合約賬戶都歸入到帳戶的一般概念是合理的,因為這些實體都是所謂的狀態物件。這些實體都有狀態

微信公眾號開發生成帶引數二維碼

公眾平臺提供了生成帶引數二維碼的介面。使用該介面可以獲得多個帶不同場景值的二維碼,使用者掃描後,公眾號可以接收到事件推送。 目前有2種類型的二維碼: 1、臨時二維碼,是有過期時間的,最長可以設定為在二維碼生成後的30天(即2592000秒)後過期,但能夠生成較多數量。臨時

web開發之Hibernate關聯關係配置

寫在前面 Hibernate中關係的對映共有以下四種:一對多、多對一、一對一、多對多這四種。 一對多單向關聯 xml對映 一對多即在A表中的每一條資料都會與B表中的n條有關聯;在這種情況下一般都是在B表新增一個欄位用來當作外來鍵與A表中的主鍵相關聯。而這種

Android開發 - 掌握ConstraintLayout複雜動畫!如此簡單!

介紹 本系列我們已經介紹了ConstraintLayout的基本用法。學習到這裡,相信你已經熟悉ConstraintLayout的基本使用了,如果你對它的用法還不瞭解,建議您先閱讀我之前的文章。 使用ConstraintLayout建立動畫的基本思想是我們建立兩個不同的佈局,每個佈局有其不同的約束,從而我

Android開發系列:對手機通訊錄的讀取、新增、刪除、查詢

一、通訊錄介紹 通訊錄是Android手機自帶的一個應用,它是一個ContentProvider應用,其它應用可以對通訊錄進行訪問,進行對聯絡人的CRUD操作。 二、通訊錄資料庫結構的介紹 首先,我們可以在File Explorer檢視下找到contacts2.db檔案,

android 開發零起步學習筆記:介面切換+幾種常用介面切換效果

兩種方法實現介面的切換: 方法1、layout切換(通過setContentView切換layout) 方法2、Activity切換 方法3、Android之fragment點選切換和滑動切換 方法1、layout切換(通過setContentView切換la

Android開發入門——推箱子游戲開發實戰

搬運工推著箱子走 本文描述推箱子游戲開發的第六步(上面的第十一步是加上前面的準備步驟)。 本文目標   本文描述如何實現搬運工推著箱子走的功能。如圖1-a所示,在遊戲介面上,當玩家用手指按下搬運工右側單元格時,搬運工將推著箱子往右走動一步。走一步之後的效

Android開發知識:讓你的應用接入微信分享,完美繞過微信分享的大坑

目錄 一、申請應用 1、首先到 [ 微信開放平臺官網] 申請註冊帳號,這些流程就忽略了到官網一看自然就知道怎麼走,感覺在這裡沒有必要說很多。 2、申請一個移動應用,填寫完成你的應用資訊。其他的沒啥,最主要的是要填對你的簽名和包名,否則SDK調

Android項目實戰:moveTaskToBack(boolean ) 方法的使用

android項目 androi ID cti 項目實戰 htm www style 順序 原文:Android項目實戰(十一):moveTaskToBack(boolean ) 方法的使用當你開發的程序被按後退鍵退出的時候, 你肯定不想讓他就這麽被finish()吧,那麽

【JMeter4.0學習】之JMeter對Mysql、Oracle數據庫性能測試腳本開發

conn 遇到的問題 mys .cn SQ 數據庫性能測試 pos rac 問題總結 一、MySQL數據庫鏈接: 註:下面所產生的問題一律參考詳見:《【JMeter4.0】之遇到的問題總結(持續更新)》(包括Mysql、Orcale) 準備:引包,包路徑一定要放對位置,

Salesforce 開發整理 定義放大鏡查找效果

!= empty ole str 技術分享 ring sql value idp 有時候在自定義的visualforce頁面上,需要實現系統標準的查找樣式,當不能使用標準的style的時候,我們只能選擇自定義實現,下面分享一個demo,預覽效果如下: 實現代碼,Visua

Android 常用開源框架源碼解析 系列 picasso 圖片框架

hand 需求 trim cor pan setname github ESS true 一、前言 Picasso 強大的圖片加載緩存框架 api加載方式和Glide 類似,均是通過鏈式調用的方式進行調用 1.1、作用 Picasso 管理整個圖片加載、轉換、緩存

主機管理+堡壘機系統開發:批量任務開發思路

erb value transfer led 不能 objects 記錄表 inf control 一、批量任務開發思路 1、開發目標 2、開發思路 1、前端提交發起請求100臺機器,那我要等待5分鐘, 問題就在於,這100臺不是同時執行完的,有可能我有10臺執行完了,執