导航栏一般配合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);
}
});
网友评论