IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 安卓TV开发遇到的那些坑 -> 正文阅读

[移动开发]安卓TV开发遇到的那些坑

	最近公司需要开发一个TV的luancher,就是那种纯物理按键的遥控,没有触摸屏,现在说说我踩得那些坑。(其实布局和代码逻辑和正常的安卓应用差不多)
	1.焦点   焦点   焦点,重要的事情说三遍,安卓TV由于没有触摸屏所以需要手动设置可以获取焦点的控件。
	2.设置获取到状态也就是常用的select。
	3.各种事件冲突。
	4.按键事件 通过重写onKeyDown(),onKeyUp()方法中监听keyCode的值,来判断用户按下的是哪个键,比如OK键  其他特殊的键值,上下左右键的话,系统会自动处理的。
其实遇到最烦的也就是焦点了,下面列举一下遇到的几件坑爹事情。

1.viewpager嵌套fragment中嵌套gridview,onkeyDown事件中的OK键的键值回调不出来,经过一系列的排查发现是系统的gridview中把以下四个键值给处理掉了。

case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_SPACE:
case KeyEvent.KEYCODE_NUMPAD_ENTER:

跟着源码查看:GridView.java中查看onKeyDown()方法:

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return commonKey(keyCode, 1, event);
   }

继续往下跟commonKey()方法:

    private boolean commonKey(int keyCode, int count, KeyEvent event) {
        if (mAdapter == null) {
            return false;
        }

        if (mDataChanged) {
            layoutChildren();
        }

        boolean handled = false;
        int action = event.getAction();
        if (KeyEvent.isConfirmKey(keyCode)
                && event.hasNoModifiers() && action != KeyEvent.ACTION_UP) {
            handled = resurrectSelectionIfNeeded();
            if (!handled && event.getRepeatCount() == 0 && getChildCount() > 0) {
                keyPressed();
                handled = true;
            }
        }

        if (!handled && action != KeyEvent.ACTION_UP) {
            switch (keyCode) {
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    if (event.hasNoModifiers()) {
                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_LEFT);
                    }
                    break;

                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    if (event.hasNoModifiers()) {
                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_RIGHT);
                    }
                    break;

                case KeyEvent.KEYCODE_DPAD_UP:
                    if (event.hasNoModifiers()) {
                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
                    } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
                    }
                    break;

                case KeyEvent.KEYCODE_DPAD_DOWN:
                    if (event.hasNoModifiers()) {
                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
                    } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
                    }
                    break;

                case KeyEvent.KEYCODE_PAGE_UP:
                    if (event.hasNoModifiers()) {
                        handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
                    } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
                    }
                    break;

                case KeyEvent.KEYCODE_PAGE_DOWN:
                    if (event.hasNoModifiers()) {
                        handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
                    } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
                    }
                    break;

                case KeyEvent.KEYCODE_MOVE_HOME:
                    if (event.hasNoModifiers()) {
                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
                    }
                    break;

                case KeyEvent.KEYCODE_MOVE_END:
                    if (event.hasNoModifiers()) {
                        handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
                    }
                    break;

                case KeyEvent.KEYCODE_TAB:
                    // TODO: Sometimes it is useful to be able to TAB through the items in
                    //     a GridView sequentially.  Unfortunately this can create an
                    //     asymmetry in TAB navigation order unless the list selection
                    //     always reverts to the top or bottom when receiving TAB focus from
                    //     another widget.
                    if (event.hasNoModifiers()) {
                        handled = resurrectSelectionIfNeeded()
                                || sequenceScroll(FOCUS_FORWARD);
                    } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                        handled = resurrectSelectionIfNeeded()
                                || sequenceScroll(FOCUS_BACKWARD);
                    }
                    break;
            }
        }

        if (handled) {
            return true;
        }

        if (sendToTextFilter(keyCode, count, event)) {
            return true;
        }

        switch (action) {
            case KeyEvent.ACTION_DOWN:
                return super.onKeyDown(keyCode, event);
            case KeyEvent.ACTION_UP:
                return super.onKeyUp(keyCode, event);
            case KeyEvent.ACTION_MULTIPLE:
                return super.onKeyMultiple(keyCode, count, event);
            default:
                return false;
        }
    }

往下跟可以看到KeyEvent.isConfirmKey()方法:

    @UnsupportedAppUsage
    public static final boolean isConfirmKey(int keyCode) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_ENTER:
            case KeyEvent.KEYCODE_SPACE:
            case KeyEvent.KEYCODE_NUMPAD_ENTER:
                return true;
            default:
                return false;
        }
    }

这里把以上4个键值给消费掉了

所以就会出现之前说的那种情况。

解决方案,重写自己的GridView继承自系统的GridView,然后重写对应方法:

public class CustomGridView extends GridView {
    public CustomGridView(Context context) {
        super(context);
    }

    public CustomGridView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomGridView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_ENTER:
            case KeyEvent.KEYCODE_SPACE:
            case KeyEvent.KEYCODE_NUMPAD_ENTER:
                return false;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_ENTER:
            case KeyEvent.KEYCODE_SPACE:
            case KeyEvent.KEYCODE_NUMPAD_ENTER:
                return false;}
        return super.onKeyMultiple(keyCode, repeatCount, event);
    }
}

使用以上自定义的GridView就能解决gridview中获取不到OK键的监听。

2.ScrollView中的子view获取不到焦点。因为不是触摸屏,所以需要获取焦点的view在xml中设置android:focusable="true"属性。还有个很重要的属性,也是平时触摸屏基本上用不到的属性android:descendantFocusability="afterDescendants"百度一下这个属性的3种值代表什么意思。

<ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:descendantFocusability="afterDescendants"
            android:scrollbars="none">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">

                <LinearLayout
                    android:id="@+id/ll_ly"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@drawable/selector1"
                    android:focusable="true"
                    android:onClick="@{activity::onClick}"
                    android:orientation="horizontal">

                    <ImageView
                        android:layout_width="@dimen/x19"
                        android:layout_height="@dimen/x19"
                        android:layout_gravity="center_vertical"
                        android:layout_marginLeft="@dimen/x4"
                        android:src="@mipmap/icon_ly" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="@dimen/x24"
                        android:layout_marginLeft="@dimen/x3"
                        android:gravity="center_vertical"
                        android:text="蓝牙"
                        android:textColor="#FFFFFF"
                        android:textSize="@dimen/x11" />
                </LinearLayout>

                <LinearLayout
                    android:id="@+id/ll_pmxm"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@drawable/selector1"
                    android:focusable="true"
                    android:onClick="@{activity::onClick}"
                    android:orientation="horizontal">

                    <ImageView
                        android:layout_width="@dimen/x19"
                        android:layout_height="@dimen/x19"
                        android:layout_gravity="center_vertical"
                        android:layout_marginLeft="@dimen/x4"
                        android:src="@mipmap/icon_pmxm" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="@dimen/x24"
                        android:layout_marginLeft="@dimen/x3"
                        android:gravity="center_vertical"
                        android:text="屏幕休眠"
                        android:textColor="#FFFFFF"
                        android:textSize="@dimen/x11" />
                </LinearLayout>

                <LinearLayout
                    android:id="@+id/ll_ld"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@drawable/selector1"
                    android:focusable="true"
                    android:onClick="@{activity::onClick}"
                    android:orientation="horizontal">

                    <ImageView
                        android:layout_width="@dimen/x19"
                        android:layout_height="@dimen/x19"
                        android:layout_gravity="center_vertical"
                        android:layout_marginLeft="@dimen/x4"
                        android:src="@mipmap/icon_ld" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="@dimen/x24"
                        android:layout_marginLeft="@dimen/x3"
                        android:gravity="center_vertical"
                        android:text="亮度"
                        android:textColor="#FFFFFF"
                        android:textSize="@dimen/x11" />
                </LinearLayout>

                <LinearLayout
                    android:id="@+id/ll_cckj"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@drawable/selector1"
                    android:focusable="true"
                    android:onClick="@{activity::onClick}"
                    android:orientation="horizontal">

                    <ImageView
                        android:layout_width="@dimen/x19"
                        android:layout_height="@dimen/x19"
                        android:layout_gravity="center_vertical"
                        android:layout_marginLeft="@dimen/x4"
                        android:src="@mipmap/icon_cckj" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="@dimen/x24"
                        android:layout_marginLeft="@dimen/x3"
                        android:gravity="center_vertical"
                        android:text="存储空间"
                        android:textColor="#FFFFFF"
                        android:textSize="@dimen/x11" />
                </LinearLayout>

                <LinearLayout
                    android:id="@+id/ll_rq"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@drawable/selector1"
                    android:focusable="true"
                    android:onClick="@{activity::onClick}"
                    android:orientation="horizontal">

                    <ImageView
                        android:layout_width="@dimen/x19"
                        android:layout_height="@dimen/x19"
                        android:layout_gravity="center_vertical"
                        android:layout_marginLeft="@dimen/x4"
                        android:src="@mipmap/icon_rq" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="@dimen/x24"
                        android:layout_marginLeft="@dimen/x3"
                        android:gravity="center_vertical"
                        android:text="日期"
                        android:textColor="#FFFFFF"
                        android:textSize="@dimen/x11" />
                </LinearLayout>

                <LinearLayout
                    android:id="@+id/ll_gybj"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@drawable/selector1"
                    android:focusable="true"
                    android:onClick="@{activity::onClick}"
                    android:orientation="horizontal">

                    <ImageView
                        android:layout_width="@dimen/x19"
                        android:layout_height="@dimen/x19"
                        android:layout_gravity="center_vertical"
                        android:layout_marginLeft="@dimen/x4"
                        android:src="@mipmap/icon_gybj" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="@dimen/x24"
                        android:layout_marginLeft="@dimen/x3"
                        android:gravity="center_vertical"
                        android:text="关于本机"
                        android:textColor="#FFFFFF"
                        android:textSize="@dimen/x11" />
                </LinearLayout>
            </LinearLayout>
        </ScrollView>

3.获取到焦点和没获取到焦点时显示的背景色不一样,这个功能在触摸屏中也经常用到的select就出来了。

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/black" android:state_focused="false" />
    <item android:drawable="@color/white1" android:state_focused="true" />
</selector>

4.RecyclerView中的item获取不到焦点的问题,或者当RecyclerView滚动时,子item的焦点就丢失了。

4.1先重写LinearLayoutManager:

public class FocusLinearLayoutManager extends LinearLayoutManager {
    public FocusLinearLayoutManager(Context context) {
        super(context);
    }

    public FocusLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public FocusLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }


    @Override
    public View onInterceptFocusSearch(View focused, int direction) {
        int count = getItemCount();//获取item的总数
        int fromPos = getPosition(focused);//当前焦点的位置
        int lastVisibleItemPos = findLastVisibleItemPosition();//最新的已显示的Item的位置
        switch (direction) {//根据按键逻辑控制position
            case View.FOCUS_UP:
                fromPos--;
                break;
            case View.FOCUS_DOWN:
                fromPos++;
                break;
        }

        Log.i("dz", "onInterceptFocusSearch , fromPos = " + fromPos + " , count = " + count+" , lastVisibleItemPos = "+lastVisibleItemPos);
        if(fromPos < 0 || fromPos >= count ) {
            //如果下一个位置<0,或者超出item的总数,则返回当前的View,即焦点不动
            if (fromPos < 0) {
                return super.onInterceptFocusSearch(focused, direction);
            }
            return focused;
        } else {
            //如果下一个位置大于最新的已显示的item,即下一个位置的View没有显示,则滑动到那个位置,让他显示,就可以获取焦点了
            if (fromPos > lastVisibleItemPos) {
                scrollToPosition(fromPos);
            }
        }
        return super.onInterceptFocusSearch(focused, direction);
    }
}

4.2 Item的根布局中添加android:focusable="true"属性和android:background="@drawable/selector1"
4.3 使用FocusLinearLayoutManager :

RecyclerView.setLayoutManager(new FocusLinearLayoutManager(this, FocusLinearLayoutManager.VERTICAL, false));

这样就解决的之前说的问题。遇到以上的坑 记录分享一下,避免再次踩坑。(总而言之,没有想象中的那么难,系统会处理很多按键的事件,就是焦点和特殊键值需要处理一下)

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-04-22 18:47:45  更:2022-04-22 18:52:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 23:03:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码