选中控件高亮/放大是TV端中极其常见的交互效果
我们知道Android 5.0之后在系统级渲染上引入了Z轴的概念,那么想要选中放大直接scale + 抬高Z轴不就好了嘛
cardView.setScaleX(1.2f);
cardView.setScaleY(1.2f);
cardView.setElevation(ScreenUtil.dp2px(6));
此题结束,下一题!
……
当然没这么简单,除非你的app设定 minSdkVersion=21
,但这目前是不可能的,之前我们提到过,AndroidTV端pre5.0的用户占比仍然是很高的,做任何效果都必须考虑Android 4.4及以下版本的兼容,elevation和translationZ在pre5.0上是不会生效的,所以我们要另寻他路。
为什么前面要提Z轴的事,因为选中放大直接scale会出现遮挡的问题,即View的渲染是有先后顺序的,后渲染的会将先渲染的控件遮挡,这个顺序基本按照xml布局中从上到下,列表中从前到后来推断。
TV端的控件选中是很有规律的,只要是选中的目标,必然拥有焦点,我们要实现选中放大也必然要在焦点上做文章,就是一点:
拥有焦点的控件最后渲染
有了方向,就该找方法了,很幸运的是Android ViewGroup给我们提供了决定View渲染顺序的方法:getChildDrawingOrder()
// code in ViewGroup.java
/**
* Returns the index of the child to draw for this iteration. Override this
* if you want to change the drawing order of children. By default, it
* returns i.
* <p>
* NOTE: In order for this method to be called, you must enable child ordering
* first by calling {@link #setChildrenDrawingOrderEnabled(boolean)}.
*
* @param i The current iteration.
* @return The index of the child to draw this iteration.
*
* @see #setChildrenDrawingOrderEnabled(boolean)
* @see #isChildrenDrawingOrderEnabled()
*/
protected int getChildDrawingOrder(int childCount, int i) {
return i;
}
我们要做的事,就是在需要实现选中放大效果的ViewGroup(LinearLayout, RelativeLayout, RecyclerView…etc.)内部重写此方法,并且按照源码注释说明,调用setChildrenDrawingOrderEnabled(true)
开启自定义渲染顺序。
/* 以RelativeLayout为例 */
public class TVRelativeLayout extends RelativeLayout {
public TVRelativeLayout(Context context) {
super(context);
init();
}
public TVRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TVRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setChildrenDrawingOrderEnabled(true);
}
/* 重写此方法,记录焦点控件在ViewGroup中的position,并将其与本该最后渲染的View对调 */
@Override
protected int getChildDrawingOrder(int childCount, int i) {
View focusedChild = getFocusedChild();
int focusViewIndex = indexOfChild(focusedChild);
if (focusViewIndex == -1) {
return i;
}
if (focusViewIndex == i) {
return childCount - 1;
} else if (i == childCount - 1) {
return focusViewIndex;
} else {
return i;
}
}
@Override
public void requestChildFocus(View child, View focused) {
super.requestChildFocus(child, focused);
invalidate();
}
}
在经过以上操作之后,这个自定义的RelativeLayout会自动将获取焦点的child放到最后一个渲染,这样在焦点Child被放大时,就不会被其它控件遮住,其它ViewGroup实现方式原理相同。