聊一聊 Leanback 中的 HorizontalGridView

云視聽極光截圖.png

??Google 的 Leanback 有其固定的風格,但國內的UI往往不會按照 Leanback 的風格進行設計,比如說上面的云視聽極光。這就導致 Android TV 的那些封裝好的 Fragment、Presenter 等控件我們基本上用不到,局限性很強,所以,如果想要既使用 Leanback 的放大效果,又要同時按國內的 UI 風格做出國內界面的樣子的話,只能使用適應性較強的控件,比如如 HorizontalGridView、VerticalGridView。那就要求我們要盡可能的熟悉 HorizontalGridView、VerticalGridView 這些控件的使用方式。
??下面就記錄一下 HorizontalGridView 相關的東西。
??HorizontalGridView 繼承自抽象類 BaseGridView,而 BaseGridView 又繼承自 RecyclerView,也就是說 HorizontalGridView 是 RecyclerView 的子類,對于 HorizontalGridView 來說,RecyclerView 有的特性基本上也適用于 HorizontalGridView,也可以認為 HorizontalGridView 就是針對 TV 再次封裝的 RecyclerView 。
??這次就對照下圖所示的云視聽極光的標題欄來寫個 demo ,順便梳理一下 HorizontalGridView 的一些屬性和方法。

image.png

1 基本使用

1.1 新建項目,添加依賴

??如果想要使用Leanback,首先要添加 如下所示的 Leanback 的依賴。

    implementation 'com.android.support:leanback-v17:28.0.0'

1.2 修改 activity_main.xml

??在 activity_main.xml 中添加標題欄的 HorizontalGridView。修改后如下所示。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:background="#000000">

    <android.support.v17.leanback.widget.HorizontalGridView
        android:id="@+id/hg_title"
        android:layout_width="wrap_content"
        android:layout_height="50dp" />

</android.support.constraint.ConstraintLayout>

1.3 創建 Presenter

??Presenter 是 HorizontalGridView 用來綁定、展示子 Item 的類,類比著想的話,它就是和 RecyclerView 的 Adapter 起著一樣的作用的東西,它們都有 onCreateViewHolder、onBindViewHolder 這樣的方法,都有繼承自 ViewHolder 的靜態內部類。看看下面 TitlePresenter 這個類就會覺得十分熟悉。
??那就分析分析吧。
??一樣之處:它們都是在 onCreateViewHolder 里加載 item 的布局,在 onBindViewHolder 里綁定數據,在內部類 ViewHolder 中接收由布局生成的子 View,并加載子 View 中的控件。
??不一樣之處:不一樣的是 RecyclerView 的 Adapter 有 notifyDataSetChanged、notifyItemChanged、notifyItemMoved、notifyItemRangeChanged 這樣的數據刷新的方法,而在 HorizontalGridView 中,這些功能都抽出來了,它們都放到了 ObjectAdapter 這個抽象類及其子類中了,這是 MVP 設計模式的寫法,同時我覺得這也體現了單一職責的思想。
??這里還有一些不關鍵的類和 xml 沒貼出來,有需要去文章末尾百度云下載項目看吧。

TitlePresenter.class

package isuperred.github.com.horizontalgeidviewdemo;

import android.support.v17.leanback.widget.Presenter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class TitlePresenter extends Presenter {
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_title, viewGroup, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) {
        if (o instanceof Title) {
            ViewHolder vh = (ViewHolder) viewHolder;
            vh.tvTitle.setText(((Title) o).getName());
        }
    }

    @Override
    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {

    }

    static class ViewHolder extends Presenter.ViewHolder {
        TextView tvTitle;

        public ViewHolder(View view) {
            super(view);
            tvTitle = view.findViewById(R.id.tv_title);
        }
    }
}

1.4 初始化 HorizontalGridView 并為之綁定適配器、數據

??先通過 findViewById 獲取 HorizontalGridView 的對象,通過 setHorizontalSpacing 這個方法在 item 之間添加間距,ArrayObjectAdapter 用于數據的綁定、數據的刷新,ItemBridgeAdapter 也可以進行數據刷新,它是全部刷新一遍,比較重,它更重要的是起一個橋梁的作用,將 ObjectAdapter、PresenterSelector、Presenter聯系起來。
MainActivity.class

package isuperred.github.com.horizontalgeidviewdemo;

import android.os.Bundle;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.FocusHighlight;
import android.support.v17.leanback.widget.FocusHighlightHelper;
import android.support.v17.leanback.widget.HorizontalGridView;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        HorizontalGridView horizontalGridView = findViewById(R.id.hg_title);
        horizontalGridView.setHorizontalSpacing(30);
        ArrayObjectAdapter arrayObjectAdapter = new ArrayObjectAdapter(new TitlePresenter());
        ItemBridgeAdapter itemBridgeAdapter = new ItemBridgeAdapter(arrayObjectAdapter);
        /*FocusHighlightHelper.setupBrowseItemFocusHighlight(itemBridgeAdapter,
                FocusHighlight.ZOOM_FACTOR_LARGE, true);*/
        horizontalGridView.setAdapter(itemBridgeAdapter);
        arrayObjectAdapter.addAll(0, TitleModel.getTitleList());

    }
}

1.5 效果圖

??代碼寫完效果就是下面這樣了,我這是在電視上運行后的效果。

效果圖.png

2 一些屬性、方法

2.1 focusOutFront、focusOutEnd

image.png

??像上面云視聽極光所示,如果標題欄使用 HorizontalGridView 實現,內容區域使用 Fragment 里放的 VerticalGridView 實現,可能出現標題欄和內容區焦點切換不成功的問題,比如說,焦點不能從內容區切到標題欄這樣的情況。這時使用 focusOutFront 和 focusOutEnd 屬性能夠解決問題,解決不同容器里焦點切換不成功的問題。使用方式如下所示。

<android.support.v17.leanback.widget.HorizontalGridView
        android:id="@+id/hg_title"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        app:focusOutEnd="true"
        app:focusOutFront="true"/>

??這2個屬性在源碼中是通過調用 GridLayoutManager 的 setFocusOutAllowed 方法來使用的。

image.png

2.2 setHorizontalSpacing

??沒什么說的,就是設置 HorizontalGridView 的 Item 之間的間距。setHorizontalMargin 已經被棄用了。

image.png

2.3 setFocusScrollStrategy

??setFocusScrollStrategy 用來設置焦點的滾動方式,它的參數有3個可選值,分別為 FOCUS_SCROLL_ALIGNEDFOCUS_SCROLL_ITEMFOCUS_SCROLL_PAGE,默認值為 FOCUS_SCROLL_ALIGNED;值得一提的是,HorizontalGridView 和 VerticalGridView 的默認焦點搜索規則是溯源原則,而設置焦點滾動方式為 FOCUS_SCROLL_ITEM 后,焦點搜索規則變為了就近原則。
setFocusScrollStrategy 使用方式如下所示。

     horizontalGridView.setFocusScrollStrategy(HorizontalGridView.FOCUS_SCROLL_ITEM);
     verticalGridView.setFocusScrollStrategy(HorizontalGridView.FOCUS_SCROLL_ITEM);

??FOCUS_SCROLL_ALIGNED:焦點在中間

焦點在中間.gif

??FOCUS_SCROLL_ITEM:焦點在末尾

焦點在末尾.gif

??FOCUS_SCROLL_PAGE:翻頁

翻頁.gif

2.4 setNumRows

??setNumRows 用于設置行數,默認 HorizontalGridView 為一行,通過 setNumRows 方法可以設置多行。但有個注意點,設置多行后要注意 position 的位置。舉個例子,2 行的 HorizontalGridView,第一行第一個模塊 position 為 0,而 position 為 1 的模塊是 第二行第一個,而非第一行第二個。如下圖所示。

image.png

2.5 setRowHeight

??注意了,setRowHeight 是用來設置 HorizontalGridView 的 Item 的高度,而不是用來設置 HorizontalGridView 的高度。

2.6 setSelectedPosition、setSelectedPositionSmooth

??setSelectedPosition 和 setSelectedPositionSmooth 都是讓某個 position 獲取焦點,區別在于 setSelectedPositionSmooth 在移動時更平滑一點。

2.7 duplicateParentState

image.png

??這個屬性還是很有用的。舉個例子,上圖是小米電視的【我的應用】頁面,體驗一下能夠感受到:它的放大的焦點 View 是每個 Item 的最外層布局,而不是圖標那個View,但是其焦點框卻套在了圖標那個 View 上,那這種效果如果我來實現就會用到 duplicateParentState 屬性了。duplicateParentState 的意思是:當前控件是否跟隨父控件的(點擊、焦點等)狀態。下面簡單寫一下其使用方式。

image.png

??如上圖所示,最外層布局設置可點擊、可獲取焦點,在 ImageView 里設置了 duplicateParentState 屬性,表示跟隨父控件狀態,所以在父控件獲取焦點時,ImageView 也可以獲取焦點。ImageView 設置了一個背景 bg_focus_border,bg_focus_border 是一個Selector,里邊設置了焦點態和非焦點態的邊框。這樣,Item 的獲取焦點時,圖標就會顯示有焦點框了。

3 代碼

??沒多少代碼,就只傳了標題欄的代碼。
??百度云盤下載代碼鏈接:https://pan.baidu.com/s/14Rf1XZMSxJQCH6dlmd4ICw
??提取碼:gkaj

4 結語

??還有很多不會的,不知道的東西,慢慢學習吧。路漫漫其修遠兮!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容