android tv开发和移动端开发最大的不同就是多了一个焦点处理的逻辑。尤其是类似Recyclerview这样本身带有滑动效果,为了醒目的显示当前焦点在什么位置,需要滑动的时候回添加大量的动画、高亮、阴影等效果。
同样,让焦点位置不变而列表主动滑动也是一种常见的提醒焦点的手段。demo效果图如下,结尾放出全部代码:
一、准备工作
先导入recyclerview
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha03'
}
我用的demo是androidx的recyclerview。低版本的同学可以使用android.support支持库。
在布局文件中添加recyclerview的布局,并添加一个item的布局。findviewbyid找到recyclerview的控件,并setLayoutManager(我用的是LinearLayoutManager)和setAdapter。一个粗糙的recyclerview效果就出来了。这是最简单的recyclerview,除了能滑动,什么效果也没有。
二、突出焦点,添加放大动画和阴影
允许item获得焦点,并为item设置焦点监听。这一步可以放到onBindViewHolder或者ViewHolder初始化的地方。
为了能看出当前焦点的位置,还需要对获得焦点的item进行高亮处理。下面代码中,用setTranslationZ添加了阴影,ofFloatAnimator方法中还设置了放大动画。
class MyHolder extends RecyclerView.ViewHolder{
public MyHolder(@NonNull final View itemView) {
super(itemView);
itemView.setFocusable(true);
itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean b) {
if(b){
int[] amount = getScrollAmount(recyclerView, view);//计算需要滑动的距离
//滑动到指定距离
scrollToAmount(recyclerView, amount[0], amount[1]);
itemView.setTranslationZ(20);//阴影
ofFloatAnimator(itemView,1f,1.3f);//放大
}else {
itemView.setTranslationZ(0);
ofFloatAnimator(itemView,1.3f,1f);
}
}
});
}
//放大动画
//放大动画
private void ofFloatAnimator(View view,float start,float end){
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(700);//动画时间
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", start, end);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", start, end);
animatorSet.setInterpolator(new DecelerateInterpolator());//插值器
animatorSet.play(scaleX).with(scaleY);//组合动画,同时基于x和y轴放大
animatorSet.start();
}
因为item放大,体积超过了recyclerview的边界。为了使这部分正常显示,需要在布局文件中recyclerview的父布局添加clipChildren和clipToPadding属性。
android:clipChildren="false"
android:clipToPadding="false"
三、计算滑动距离,使焦点始终居中
首先我们看看recyclerview源码是怎么控制滑动的距离的,什么时候需要滑动,什么时候不用滑动。在源码RecyclerView/的LayoutManager中有这样一段代码:
public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent,
@NonNull View child, @NonNull Rect rect, boolean immediate,
boolean focusedChildVisible) {
int[] scrollAmount = getChildRectangleOnScreenScrollAmount(child, rect
);
int dx = scrollAmount[0];
int dy = scrollAmount[1];
if (!focusedChildVisible || isFocusedChildVisibleAfterScrolling(parent, dx, dy)) {
if (dx != 0 || dy != 0) {
if (immediate) {