美文网首页
关于AndroidTV导航栏问题

关于AndroidTV导航栏问题

作者: 飞奔吧牛牛 | 来源:发表于2019-10-21 17:30 被阅读0次
    导航栏一般配合ViewPager一起使用,在手机中使用导航栏可以选择官方api中的TabLayout,或者其他导航栏框架,但是在AndroidTV中却不能使用。原因就在于两者的交互方式有很大差别。手机中直接响应onClick事件或处理onTouch事件。但在TV上,事件是通过遥控器产生,通过处理onKeyDown或onKeyUp来对事件进行处理。

    由于刚刚接触AndroidTV开发很多问题还不知道如何下手。
    之前的思路是导航栏的items是一个Button列表,当某一个Button获取焦点时设置ViewPager对应的currentView,反之亦然。

    具体做法:

    给button setOnFocusChangeListener,当焦点改变时:
    1.设置button的样式。
    2.改变ViewPager显示的View。
    当焦点在ViewPager中的不同view之间切换时:
    1.设置对应button的样式。
    但问题就在于,当焦点从ViewPager切换到导航栏中的Button时,总是最后一个Button获取到焦点,这时由于最后一个Button获取到焦点,ViewPager又切换到了最后一个。
    我们要在ViewPager中的某个View向上切换焦点时,设置对应的Button获取焦点,但是又要怎么样监听向上切换焦点这个动作呢?嗯......事情变得有点棘手了,ViewPager中的View那么多,每一个都setOnFocusChangeListener吗?显然这条路走上了死胡同。放弃。。。又想,能不能给viewPager setOnKeyListener,设置回调方法,向上切换时,设置一个flag。在导航栏button获取焦点时,首先判断flag,得到上个焦点是不是ViewPager,如果是,获取ViewPager当前View的位置,设置对应的Button获取焦点。可实际情况却是设置的OnKeyListener并没有得到调用。嗯。。。。。。也好,因为即使这种方法可行,也总感觉好傻~~!

    上网找解决方案吧,于是发现了这个,激动的我都快哭了
    https://www.jianshu.com/p/cf818a09f756 感谢。

    正确的思路:
    自定义导航栏NavigationBar继承自LinearLayout,里面有若干TextView,NavigationBar必须setFocusable(true)。重写
    onKeyDown方法,当keyCode为左右键时,设置对应的TextView的样式。
    嗯~,这下就简单多了。有了思路剩下的就是各种细节和逻辑了。

    自定义属性

    分析
    希望设置的导航栏属性包括:字体大小、颜色、被选中时颜色、item宽度、内外间距、默认选中的位置、指示器Drawable资源。以及item的宽度模式:固定大小,或,随内容大小。固定大小时,所有item宽度被指定,设置水平方向padding不起作用。随内容大小时,item宽度根据内容宽度变化,设置item的宽度不起作用。
    代码

    <resources>
        <declare-styleable name="NavigationBar">
            <attr name="nav_textSize" format="reference|dimension" />
            <attr name="nav_textColorNormal" format="reference|color" />
            <attr name="nav_textColorFocuse" format="reference|color" />
            <!--item大小是固定的还是根据内容变化的-->
            <attr name="nav_itemMode" format="enum">
                <!--大小固定-->
                <enum name="fixed" value="0" />
                <!--大小随内容改变-->
                <enum name="auto" value="1" />
            </attr>
            <!--每个item宽度,如果nav_itemMode为auto,则该属性不起作用-->
            <attr name="nav_itemWidth" format="reference|dimension|enum">
                <enum name="wrap_content" value="-1" />
            </attr>
            <!--水平方向上的Padding,如果nav_itemMode为fixed,则该属性不起作用-->
            <attr name="nav_itemPaddingH" format="reference|dimension" />
            <!--垂直方向上的padding-->
            <attr name="nav_itemPaddingV" format="reference|dimension" />
            <!--item水平方向上的margin-->
            <attr name="nav_itemMargin" format="reference|dimension" />
            <!--默认选中位置-->
            <attr name="nav_default_pos" format="integer"/>
            <!--指示器-->
            <attr name="nav_itmeIndicator" format="reference|integer" />
        </declare-styleable>
    </resources>
    

    获取自定义属性

      public NavigationBar(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            if (attrs != null) {
                TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.NavigationBar);
                mTextSize = typedArray.getDimensionPixelSize(R.styleable.NavigationBar_nav_textSize, 20);
                mTextColorNormal = typedArray.getColor(R.styleable.NavigationBar_nav_textColorNormal, Color.WHITE);
                mTextColorFocuse = typedArray.getColor(R.styleable.NavigationBar_nav_textColorFocuse, Color.YELLOW);
                mItemMode = typedArray.getInt(R.styleable.NavigationBar_nav_itemMode, ITME_MODE_AUTO);
                mItemWidth = typedArray.getDimensionPixelOffset(R.styleable.NavigationBar_nav_itemWidth, 0);
                mItemPaddingH = typedArray.getDimensionPixelOffset(R.styleable.NavigationBar_nav_itemPaddingH, 0);
                mItemPaddingV = typedArray.getDimensionPixelOffset(R.styleable.NavigationBar_nav_itemPaddingV, 0);
                mItemMargin = typedArray.getDimensionPixelSize(R.styleable.NavigationBar_nav_itemMargin, 20);
                mDefaultPos = typedArray.getInt(R.styleable.NavigationBar_nav_default_pos, 0);
                mItemIndicatorBackground = typedArray.getDrawable(R.styleable.NavigationBar_nav_itmeIndicator);
                typedArray.recycle();
            }
            initView();
            setFocusable(true);
        }
    

    使用自定义属性

        <com.app.android.lib_widget.NavigationBar
            android:id="@+id/navigationBar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="50dp"
            app:nav_default_pos="1"
            app:nav_itemMargin="30dp"
            app:nav_itemMode="auto"
            app:nav_itemWidth="20dp"
            app:nav_itmeIndicator="@drawable/selector_item_indicator"
            app:nav_textSize="20dp">
    

    全部属性

     public static final int ITME_MODE_FIXED = 0;    //固定大小
        public static final int ITME_MODE_AUTO = 1;     //随内容大小
        private int mTextSize;
        private int mTextColorNormal;
        private int mTextColorFocuse;
        private int mItemMode;
        private int mItemWidth;
        private int mItemPaddingH;
        private int mItemPaddingV;
        private int mItemMargin;
        private int mDefaultPos;
        private Drawable mItemIndicatorBackground;
    
        private List<String> mData;
        private int mCurrentSelectedPosition;
        private OnItemSelectedChangeListener mOnItemSelectedChangeListener;
        private LinearLayout mItemContainer;
        private View mIndicatorView;
        private SparseIntArray mToLeftMap = new SparseIntArray();
    
    

    初始化View

    导航栏主体是两个LinearLayout嵌套,下面是个指示器


    image.png
    public class NavigationBar extends LinearLayout {
    ...
    
        private void initView() {
            setOrientation(VERTICAL);
            //标题容器
            mItemContainer = new LinearLayout(getContext());
            mItemContainer.setOrientation(HORIZONTAL);
            mItemContainer.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            //指示器
            mIndicatorView = new View(getContext());
            mIndicatorView.setLayoutParams(new ViewGroup.LayoutParams(1, 6));
            mIndicatorView.setBackground(mItemIndicatorBackground);
            addView(mItemContainer);
            addView(mIndicatorView);
        }
    }
    

    当设置内容时,刷新页面

       public void setData(List<String> mData) {
            this.mData = mData;
            refreshView();
        }
    
        private void refreshView() {
            mItemContainer.removeAllViews();
            mToLeftMap.clear();
            //添加标题
            for (int i = 0; i < mData.size(); i++) {
                View view = initItemView(mData.get(i));
                mItemContainer.addView(view);
            }
            mCurrentSelectedPosition = mDefaultPos;
            for (int i = 0; i <= mItemContainer.getChildCount() - 1; i++) {
                final int finalI = i;
                final TextView child = (TextView) mItemContainer.getChildAt(i);
                child.getViewTreeObserver().addOnGlobalLayoutListener((new ViewTreeObserver.OnGlobalLayoutListener() {
                    public void onGlobalLayout() {
                        //指示器中点和标题中点对齐,所以,获取所有标题的中点位置
                        child.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        int left = child.getWidth() / 2 + child.getLeft();//每个item中点到父布局左边的距离
                        mToLeftMap.put(finalI, left);
                        if (mDefaultPos == finalI) {
                            setCurrentViewSelected();
                            moveIndictor(left);
                        }
                    }
                }));
            }
        }
      private View initItemView(String text) {
            TextView textView = new TextView(getContext());
            textView.setText(text);
            textView.setGravity(Gravity.CENTER);
            textView.setTextColor(new ColorStateList(
                    new int[][]{{android.R.attr.state_selected}, new int[0]},
                    new int[]{mTextColorFocuse, mTextColorNormal}));
            textView.setTextSize(mTextSize);
            if (mItemMode == ITME_MODE_FIXED) {
                //固定大小,itemWidth起作用
                LayoutParams layoutParams = new LayoutParams(mItemWidth, LayoutParams.WRAP_CONTENT);
                layoutParams.setMargins(mItemMargin, 0, mItemMargin, 0);
                textView.setLayoutParams(layoutParams);
            } else if (mItemMode == ITME_MODE_AUTO) {
                LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
                layoutParams.setMargins(mItemMargin, 0, mItemMargin, 0);
                textView.setLayoutParams(layoutParams);
                textView.setPadding(mItemPaddingH, mItemPaddingV, mItemPaddingH, mItemPaddingV);
            }
            return textView;
        }
    

    设置按键事件

    监听左右方向按键,控制具体要哪个item显示为选中状态

      @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_DOWN) switch (keyCode) {
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    if (mCurrentSelectedPosition > 0 && mCurrentSelectedPosition <= mData.size()) {
                        setCurrentViewUnSelected();
                        mCurrentSelectedPosition--;
                        setCurrentViewSelected();
                        if (mItemIndicatorBackground != null) {
                            moveIndictor(mToLeftMap.get(mCurrentSelectedPosition));
                        }
                        if (mOnItemSelectedChangeListener != null) {
                            mOnItemSelectedChangeListener.onItemSelected(mCurrentSelectedPosition + 1, mCurrentSelectedPosition);
                        }
                    }
                    return true;
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    if (mCurrentSelectedPosition >= 0 && mCurrentSelectedPosition < mData.size() - 1) {
                        setCurrentViewUnSelected();
                        mCurrentSelectedPosition++;
                        setCurrentViewSelected();
                        if (mItemIndicatorBackground != null) {
                            moveIndictor(mToLeftMap.get(mCurrentSelectedPosition));
                        }
                        if (mOnItemSelectedChangeListener != null) {
                            mOnItemSelectedChangeListener.onItemSelected(mCurrentSelectedPosition - 1, mCurrentSelectedPosition);
                        }
                    }
                    return true;
            }
            return super.onKeyDown(keyCode, event);
        }
    
    
        //恢复mCurrentSelectedPosition为不选中
        private void setCurrentViewUnSelected() {
            View textView = mItemContainer.getChildAt(mCurrentSelectedPosition);
            textView.setSelected(false);
            ((TextView) textView).setShadowLayer(0.0F, 0.0F, 0.0F, Color.RED);
            textView.animate().scaleX(1f).scaleY(1f).start();
        }
    
        //设置mCurrentSelectedPosition为选中
        private void setCurrentViewSelected() {
            View textView = mItemContainer.getChildAt(mCurrentSelectedPosition);
            textView.setSelected(true);
            ((TextView) textView).setShadowLayer(25.0F, 0.0F, 0.0F, Color.RED);
            textView.animate().scaleX(1.2f).scaleY(1.2f).start();
        }
    
        //移动指示器
        private void moveIndictor(int location) {
            View childAt = mItemContainer.getChildAt(mCurrentSelectedPosition);
            int width = childAt.getWidth();
            int width1 = mIndicatorView.getLayoutParams().width;
            mIndicatorView.animate().translationX(location - width1 / 2f).scaleX(width * 1f / width1).start();
        }
    
    

    使用

    navigationBar.setData(data = Arrays.asList("电影", "电视剧", "综艺", "科技", "纪录片"));
            navigationBar.setmOnItemSelectedChangeListener(new NavigationBar.OnItemSelectedChangeListener() {
                @Override
                public void onItemSelected(int oldPos, int newPos) {
                    viewPager.setCurrentItem(newPos);
                }
            });
    

    相关文章

      网友评论

          本文标题:关于AndroidTV导航栏问题

          本文链接:https://www.haomeiwen.com/subject/uzbxmqtx.html