1. 程式人生 > >Android TV橫向滾動網格佈局——RecyclerView的使用

Android TV橫向滾動網格佈局——RecyclerView的使用

最近在做一個Android盒子的專案,主要是Launcher有一個橫向滾動的介面。主要使用的是RecyclerView。總結一下。

一、先了解下RecyclerView
RecyclerView是類似於ListView、GridView的一種AdapterView。相比較的優勢是使用更加靈活,可以滿足實現更多不同的效果。

在我要實現的水平滾動網格佈局中就得到了很好的滿足。因為使用HorizentalScrollView + GridView的模式會十分複雜,並且焦點、動作的監聽會比較混亂。事件衝突處理起來特別麻煩。

二、實現簡單例子
記錄一下我自己學習的過程。先寫了一下RecyclerViewTest的工程。這個工程的主要效果是在頁面上顯示Android裝置上所安裝的所有應用。並且點選應用圖示可以進入相應的應用。點選選單鍵可以解除安裝該應用。

大概就是這樣:
這裡寫圖片描述

1.先要關聯recyclerview的jar包:

dependencies {
    ...
    compile 'com.android.support:recyclerview-v7:23.0.1'
}

2.然後就可以使用RecyclerView了,先根據需求自定義了一個SimpleRecyclerView.java

public class SimpleRecycleView extends RecyclerView {
    private static final String TAG = SimpleRecycleView.class.getSimpleName();
    // 一個滾動物件
private Scroller mScroller; private int mLastX = 0; public SimpleRecycleView(Context context) { super(context); init(context); } public SimpleRecycleView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public
SimpleRecycleView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } // 一個初始化方法,傳入了一個上下文物件,用來初始化滾動物件 private void init(Context context){ mScroller = new Scroller(context); } // 重寫了計算滾動方法 @Override public void computeScroll() { if(mScroller!=null && mScroller.computeScrollOffset()){ scrollBy(mLastX - mScroller.getCurrX(), 0); mLastX = mScroller.getCurrX(); postInvalidate(); } } /** * 呼叫此方法滾動到目標位置,其中(fx, fy)表示最終要滾到的目標位置的座標值 * duration表示期間滾動的耗時。 * * @param fx 目標位置的X向座標值 * @param fy 目標位置的Y向座標值 * @param duration 滾動到目標位置所消耗的時間毫秒值 */ @SuppressWarnings("unused") public void smoothScrollTo(int fx, int fy,int duration) { int dx = 0; int dy = 0; // 計算變化的位移量 if(fx != 0) { dx = fx - mScroller.getFinalX(); } if(fy!=0) { dy = fy - mScroller.getFinalY(); } Log.i(TAG, "fx:" + fx + ", getFinalX:" + mScroller.getFinalX() + ", dx:" + dx); smoothScrollBy(dx, dy, duration); } /** * 呼叫此方法設定滾動的相對偏移 */ public void smoothScrollBy(int dx, int dy, int duration) { if(duration > 0) { mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy, duration); } else { // 設定mScroller的滾動偏移量 mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy); } // 重繪整個view,重繪過程會呼叫到computeScroll()方法。 // 這裡必須呼叫invalidate()才能保證computeScroll()會被呼叫,否則不一定會重新整理介面,看不到滾動效果 invalidate(); } /** * 此方法用來檢查自動調節 * * @param position 要檢查的位置 */ @SuppressWarnings("unused") public void checkAutoAdjust(int position){ int childCount = getChildCount(); // 獲取可視範圍內的選項的頭尾位置 int firstVisibleItemPosition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition(); int lastVisibleItemPosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition(); Log.d(TAG, "childCount:" + childCount + ", position:" + position + ", firstVisibleItemPosition:" + firstVisibleItemPosition + " lastVisibleItemPosition:" + lastVisibleItemPosition); if(position == (firstVisibleItemPosition + 1) || position == firstVisibleItemPosition){ // 當前位置需要向右平移 leftScrollBy(position, firstVisibleItemPosition); } else if (position == (lastVisibleItemPosition - 1) || position == lastVisibleItemPosition){ // 當前位置需要向左平移 rightScrollBy(position, lastVisibleItemPosition); } } private void leftScrollBy(int position, int firstVisibleItemPosition){ View leftChild = getChildAt(0); if(leftChild != null){ int startLeft = leftChild.getLeft(); int endLeft = (position == firstVisibleItemPosition ? leftChild.getWidth() : 0); Log.d(TAG, "startLeft:" + startLeft + " endLeft" + endLeft); autoAdjustScroll(startLeft, endLeft); } } private void rightScrollBy(int position, int lastVisibleItemPosition){ int childCount = getChildCount(); View rightChild = getChildAt(childCount - 1); if(rightChild != null){ int startRight = rightChild.getRight() - getWidth(); int endRight = (position == lastVisibleItemPosition ? (-1 * rightChild.getWidth()) : 0); Log.d(TAG,"startRight:" + startRight + " endRight:" + endRight); autoAdjustScroll(startRight, endRight); } } /** * * @param start 滑動起始位置 * @param end 滑動結束位置 */ private void autoAdjustScroll(int start, int end){ mLastX = start; mScroller.startScroll(start, 0, end - start, 0); postInvalidate(); } /** * 將指定item平滑移動到整個view的中間位置 * @param position 指定的item的位置 */ public void smoothScrollMaster(int position) { // 這個方法是為了設定Scroller的滾動的,需要根據業務需求,編寫演算法。 } }

3.然後就可以在佈局檔案中使用自定義的控制元件了:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/home_background">

    <com.jiuzhou.porter.launcher.widget.SimpleRecycleView
        android:id="@+id/home_apps"
        android:layout_marginLeft="@dimen/px_positive_80"
        android:layout_marginRight="@dimen/px_positive_80"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scrollbars="none" />
</RelativeLayout>

我去不小心暴露了我的包名(@^_^@)

4.在MainActivity.java中編寫程式碼:

//初始化RecyclerView,設定佈局管理器、間距、介面卡、資料等
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main)
    ...
    // 1.初始化SimpleRecyclerView
    mRecyclerView = (SimpleRecycleView) findViewById(R.id.home_apps);
    // 2.使用自定義的工具類獲得裝置中安裝的APP
    mListOfApps = LauncherCommonUtils.getAllApk(this);
    // 3.初始化介面卡
    SimpleRecyclerAdapter mAdapter = new SimpleRecyclerAdapter(this, mListOfApps);
    mRecyclerView.setItemAnimator(new DefaultItemAnimator());
    // 4.設定佈局管理器:瀑布流式
    StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(3 , StaggeredGridLayoutManager.HORIZONTAL);
    // 5.根據需要設定間距等其他內容
    mRecyclerView.setLayoutManager(staggeredGridLayoutManager);
    int right = (int) getResources().getDimension(R.dimen.px_positive_5);
    int bottom = (int) getResources().getDimension(R.dimen.px_positive_1);
    RecyclerView.ItemDecoration spacingInPixel = new SpaceItemDecoration(right, bottom);
    mRecyclerView.addItemDecoration(spacingInPixel);
    // 6.關聯介面卡
    mRecyclerView.setAdapter(mAdapter);
}
  • 這裡有必要說一下:
    關於佈局管理器的內容:
    RecyclerView的使用時是必須設定佈局管理器的。因為不同的佈局管理器決定了展現出來的演示是怎樣的。
    常見的集中佈局管理器有LinearLayoutManager、RelativeLayoutManager、GridLayoutManager、StaggeredGridLayoutManager等。
    意思一目瞭然。只提一下StaggeredGridLayoutManager,這個是水平方向的Grid

這裡比較重要的是Adapter
5. 關於SimpleRecyclerAdapter

/*
 *  Copyright (c) 2016.  Project Launcher
 *  Source SimpleRecyclerAdapter
 *  Author 沈煜
 *  此原始碼及相關文件等附件由 沈煜 編寫,作者保留所有權利
 *  使用必須註明出處。
 *  The code and documents is write by the author. All rights are reserved.
 *  Use must indicate the source.
 *
 */
public class SimpleRecyclerAdapter extends RecyclerView.Adapter<SimpleRecyclerAdapter.ViewHolder>{
    private static final String TAG = SimpleRecyclerAdapter.class.getSimpleName();
    private LayoutInflater mInflater;
    private List<AppBean> mListOfApps;
    private int currentPosition = 0;
    private Context context;

    public SimpleRecyclerAdapter(Context context, List<AppBean> mListOfApps){
        mInflater = LayoutInflater.from(context);
        this.context = context;
        this.mListOfApps = mListOfApps;
    }

    @SuppressWarnings("unused")
    public void setData(List<AppBean> mListOfApps){
        this.mListOfApps = mListOfApps;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.item_grid_apps, parent, false);
        ViewHolder vh = new ViewHolder(view);
        vh.mImageView = (ImageView) view.findViewById(R.id.home_grid_item_icon);
        vh.mTextView = (TextView) view.findViewById(R.id.home_grid_item_name);
        return vh;
    }

    private View mOldFocus;
    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        holder.mImageView.setImageDrawable(mListOfApps.get(position).getAppIcon());
        holder.mTextView.setText(mListOfApps.get(position).getAppName());

        // 設定itemView可以獲得焦點
        holder.itemView.setFocusable(true);
        holder.itemView.setTag(position);
        holder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                    currentPosition = (int) holder.itemView.getTag();
                    mOnItemSelectListener.onItemSelect(holder.itemView, currentPosition);

                    if (v != mOldFocus) {
                        View vb = v.findViewById(R.id.home_back_2);
                        GradientDrawable gd = (GradientDrawable) vb.getBackground();
                        int width = (int) context.getResources().getDimension(R.dimen.px_positive_3);
                        int color = context.getResources().getColor(R.color.color0);
                        int radius = (int) context.getResources().getDimension(R.dimen.px_positive_25);
                        gd.setStroke(width, color);
                        gd.setCornerRadius(radius);

                        if (mOldFocus != null) {
                            View ovb = mOldFocus.findViewById(R.id.home_back_2);
                            GradientDrawable ogd = (GradientDrawable) ovb.getBackground();
                            ogd.setStroke(0, Color.parseColor("#00000000"));
                        }
                    }
                    mOldFocus = v;
                } else {
                    if (v != null) {
                        View ovb2 = v.findViewById(R.id.home_back_2);
                        GradientDrawable ogd2 = (GradientDrawable) ovb2.getBackground();
                        ogd2.setStroke(0, Color.parseColor("#00000000"));
                    }
                }
            }
        });

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mOnItemClickListener.onItemClick(v, currentPosition);
            }
        });

        holder.itemView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                mOnItemKeyListener.OnItemKey(v, keyCode, event, currentPosition);
                return false;
            }
        });
    }

    @Override
    public int getItemCount() {
        return mListOfApps.size();
    }

    private int index = 0;
    class ViewHolder extends RecyclerView.ViewHolder{
        ImageView mImageView;
        TextView mTextView;

        ViewHolder(View itemView) {
            super(itemView);
            ImageView back2 = (ImageView) itemView.findViewById(R.id.home_back_2);
            GradientDrawable background = (GradientDrawable) back2.getBackground();
            TypedArray ta = context.getResources().obtainTypedArray(R.array.appBackgroundColors);
            int count = ta.length();
            int [] colorsArray = new int[count];
            for (int i=0;i<count;i++) {
                int resId = ta.getResourceId(i, -1);
                colorsArray[i] = resId;
            }
            /*Random random = new Random();
            int index = random.nextInt(count);
            while (oldIndex == index) {
                index = random.nextInt();
            }
            oldIndex = index;*/
            background.setColor(context.getResources().getColor(colorsArray[index]));
            if (index < count - 1) {
                index += 1;
            } else {
                index = 0;
            }

            ta.recycle();
        }
    }

    private OnItemSelectListener mOnItemSelectListener;
    private OnItemClickListener mOnItemClickListener;
    private OnItemLongClickListener mOnItemLongClickListener;
    private OnItemKeyListener mOnItemKeyListener;

    public interface OnItemSelectListener {
        void onItemSelect(View view, int position);
    }

    public interface OnItemClickListener {
        void onItemClick(View view, int position);
    }

    public interface OnItemLongClickListener {
        void onItemLongClick(View view, int position);
    }

    public interface OnItemKeyListener {
        void OnItemKey(View view, int keyCode, KeyEvent event, int position);
    }

    public void setOnItemSelectListener(OnItemSelectListener listener){
        mOnItemSelectListener = listener;
    }

    public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
        this.mOnItemClickListener = mOnItemClickListener;
    }

    public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {
        this.mOnItemLongClickListener = mOnItemLongClickListener;
    }

    public void setOnItemKeyListener(OnItemKeyListener mOnItemKeyListener) {
        this.mOnItemKeyListener = mOnItemKeyListener;
    }

}

最後有一個重要的地方就是Adapter中寫了許多監聽器。這個是RecyclerView特有的,因為RecyclerView沒有監聽器!!!(簡直了,不能忍好嘛。所以要在Adapter中自己定義監聽。因為你監聽的是其中的itemView,當然了也可以去RecyclerView裡面寫諸如OnItemClick這樣的監聽器,會麻煩一點。不過那樣封裝起來比較牛。去GitHub上應該有這樣的jar可以用。)

差不多了吧

————————-不怎麼華麗的分割線—————–

MDZZ,這個裡面的焦點控制忘了寫!!!厲害了我的哥,下期詳解。

相關推薦

Android TV橫向滾動網格佈局——RecyclerView的使用

最近在做一個Android盒子的專案,主要是Launcher有一個橫向滾動的介面。主要使用的是RecyclerView。總結一下。 一、先了解下RecyclerView RecyclerView是類似於ListView、GridView的一種Adap

Android--(9)--詳解網格佈局(GridLayout)

GridLayout佈局特點:將整個螢幕分成行*列的形式,每個網格上放一個元件; 在往網格中放置控制元件的時候, 會自動按照水平或垂直方向新增。 幾個常用屬性 android:rowCou

Android TV端的(RecyclerView)水平滾動焦點錯亂問題

boolean uestc spa cas roi enabled ati pix eve package com.hhzt.iptv.ui.customview;import android.content.Context;import android.content.r

Android RecyclerView網格佈局的學習

最近用到 RecyclerView的網格佈局,簡單學習了一個Demo,效果如下 下面是程式碼 public class GridViewDemo extends AppCompatActivity { private RecyclerView recyclerView

Android RecyclerView +SnapHelper 實現橫向滾動自動滾動到中心控制元件並選中

效果圖 此效果已被產品砍掉,所以有些適配bug就不修改了 此部落格只為記錄下程式碼 默哀3秒 1秒 2秒 3秒 程式碼 佈局檔案 <?xml version="1.0" encoding="utf-8"?> <

Android網格佈局實現--recyclerview

接上一篇內容,使用RecyclerView實現;<android.support.v7.widget.RecyclerView    android:id="@+id/rvGrid"    android:layout_width="match_parent"    a

AndroidTV端助力 listview與recyclerview上下聯動

pre get reat ins -s tlist ping cto desc 首先是主布局fragment裏面的xml文件 <?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:andro

Android Studio 相對佈局/網格佈局

1. 相對佈局(RelativeLayout) 1.1 相對佈局視窗內子元件的位置總是相對兄弟元件、父容器來決定的,因此叫相對佈局 1.2 如果A元件位置是由B元件的位置決定的,Android要求先定B元件,再定義A元件       如果A元件位置是由

Android網格佈局

網格佈局GridLayout 1.常用屬性rowCount(行數)、columnCount(列數)  GridLayout中子控制元件相關屬性:layout_gravity="fill_horizontal(水平填充)|fill_vertical(垂直填充)"   &

Android佈局網格佈局

1. 什麼是佈局    就是把介面中的控制元件按照某種規律擺放到指定的位置 2. 佈局的二種實現    程式碼    xml配置檔案:res/layout目錄下      注:也可以同時使用xml和程式

Android相對佈局網格佈局

案例一:相對佈局 相對佈局(重點) 1.1 相對佈局視窗內子元件的位置總是相對兄弟元件、父容器來決定的,因此叫相對佈局 1.2 如果A元件位置是由B元件的位置決定的,Android要求先定B元件,再定義A元件 注1:注意XML中元件的順序,不然會報錯 注2:

android的相對佈局網格佈局

案例一:相對佈局 相對佈局(重點) 1.1 相對佈局視窗內子元件的位置總是相對兄弟元件、父容器來決定的,因此叫相對佈局 1.2 如果A元件位置是由B元件的位置決定的,Android要求先定B元件,再定義A元件 如果A元件位置是由B元件的位置決定的,Android要求先定

flex佈局下el-table橫向滾動條失效

如下圖,是一種常見的頁面結構,我們可以有很多方法實現,inline-block,float,flex等等 但是,最近專案中遇到一個怪事,左邊是側邊欄導航,右邊是一個數據展示table,el-table的橫向滾動條死活不出來。 我是採用flex佈局,這裡簡單貼一下css原始碼 : (page 頁面根容器 s

Android 應用開發(50)---GridLayout(網格佈局)

GridLayout(網格佈局) 今天要介紹的佈局是Android 4.0以後引入的一個新的佈局,和前面所學的TableLayout(表格佈局) 有點類似,不過他有很多前者沒有的東西,也更加好用, 可以自己設定佈局中元件的排列方式 可以自定義網格佈局有多少行,多少

Android佈局2(相對佈局網格佈局

1. 相對佈局(RelativeLayout 重點:)   1.1 相對佈局視窗內子元件的位置總是相對兄弟元件、父容器來決定的(就是根據旁邊的足跡來設定位置),因此叫相對佈局   1.2 如果A元件位置是由B元件的位置決定的,Android要求先定B元件,再定義A元件  

Android-仿千度尺的橫向滾動選擇器

先上一個效果圖 主要核心方法,這裡有一個問題ontouchEvent如果返回super,則move事件不會繼續執行,down事件則沒問題.所以這裡要返回true 其次,就是計算滾動距離的問題 @Override public boolean onTouchEvent(Mot

Android 實現一個簡易橫向流式佈局

SimpleFlowLayout:一個簡易的橫向流式佈局,只實現核心功能,使用者可自行擴充套件   Demo圖片如下所示: SimpleFlowLayout直接繼承自ViewGroup,主要負責

Android 小樣之TextView橫向滾動(跑馬燈效果)

偶爾做app的時候由於文字過多,但是又不想換行顯示,影響整體佈局效果,可以使用文字橫向滾動效果。 使用繼承Android原生TextView控制元件實現 設定xml檔案TextView屬性

Android之ScrollView滾動佈局控制元件使用以及顯示新聞網頁

ScrollView滾動佈局使用原理: ①滾動產生的條件是,裡面的內容大於物理尺寸 ②ScrollView裡面只有一個子元素,這個子元素就是一個線性佈局LinearLayout,我們可以線上性佈局中新增我們需要的內容,所以ScrollView中得包裹一層,並且線性佈局中設計

RecyclerView實現標題-網格,標題-網格佈局

公司讓實現標題--網格,標題--網格的佈局,想來想去就用Recyclerview來實現了,說實話,中間休息了一段時間,recyclerview我用的並不多,為了工作,只能向前。。。後來在網上也是各種扒資料,最終實現了效果,雖然效果實現了,但是裡面還有些地方是自己不太明白的,特