AndroidTV開發(十一)Android Tv Launcher自定義RecyclerView
前言
Android TV Launcher頁在RecyclerView出來之前大家用GridView去實現(本人的FocusView)。TV開發有五向鍵的監聽,遙控器hover監聽,點選事件等。用GridView去處理焦點是有一定挑戰性的,往往會出現不可預料焦點錯亂問題。這裡封裝了一個針對TV的RecyclerView,很方便的處理了這些事件。
開始線上效果圖:
本次封裝了RecyclerView需實現了以下功能:
- 響應五向鍵,按下五向鍵的上下左右會跟著移動,並獲得焦點,在獲得焦點時會擡高
- 在滑鼠hover在條目上時會獲得焦點。
- 添加了條目的點選和長按事件。
- 添加了是否第一個可見條目和是否是最後一個可見條目的方法。
- 在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臺執行完了,執