我的编程空间,编程开发者的网络收藏夹
学习永远不晚

android TV常见需求,焦点item保持居中 —— RecyclerView自定义焦点滑动位置和滑动速度。

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

android TV常见需求,焦点item保持居中 —— RecyclerView自定义焦点滑动位置和滑动速度。

  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) {
                        parent.scrollBy(dx, dy);
                    } else {
                        parent.smoothScrollBy(dx, dy);
                    }
                    return true;
                }
            }
            return false;
        }

  可以看出recyclerview是调用

smoothScrollBy(dx, dy)
方法滑动的,而滑动的距离由
getChildRectangleOnScreenScrollAmount
方法计算得出。所以我重写这个方法,改变的dx和dy大小,或者,我也可以在其他地方主动调用smoothScrollBy方法,只要item能移动到对应位置就可以了。这里,我决定在item焦点监听中主动调用smoothScrollBy方法。计算dx和dy我参考了getChildRectangleOnScreenScrollAmount的计算方式,把获得焦点的item放在最中间。


            private int[] getScrollAmount(RecyclerView recyclerView, View view) {
                int[] out = new int[2];
                final int parentLeft = recyclerView.getPaddingLeft();
                final int parentTop = recyclerView.getPaddingTop();
                final int parentRight = recyclerView.getWidth() - recyclerView.getPaddingRight();
                final int childLeft = view.getLeft() + 0 - view.getScrollX();
                final int childTop = view.getTop() + 0 - view.getScrollY();
                final int dx =childLeft - parentLeft - ((parentRight - view.getWidth()) / 2);//item左边距减去Recyclerview不在屏幕内的部分,加当前Recyclerview一半的宽度就是居中
                final int dy = childTop - parentTop - (parentTop - view.getHeight()) / 2;//同上
                out[0] = dx;
                out[1] = dy;
                return out;
            }
        }

getScrollAmount
是源码
getChildRectangleOnScreenScrollAmount
的简化版本。
然后再item的焦点监听中,调用滑动就可以了

				itemView.setFocusable(true);
                itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
                    @Override
                    public void onFocusChange(View view, boolean b) {
                        if(b){
                            int[] amount = getScrollAmount(recyclerView, view);//计算需要滑动的距离
                            recyclerView.smoothScrollBy(amount[0], amount[1]);
                            itemView.setTranslationZ(20);//阴影
                            ofFloatAnimator(itemView,1f,1.3f);//放大
                        }else {
                            itemView.setTranslationZ(0);
                            ofFloatAnimator(itemView,1.3f,1f);
                        }
                    }
                });
四、修改
smoothScrollBy
的滑动速度并添加动画插值器

  到上一步,效果就基本完成了。为了更精细的完成界面,还可以对滑动的速度和滑动效果进行修改。
  我们已经知道recyclerview源码中使用的是

smoothScrollBy(dx, dy)
来进行滑动,那么跟踪
smoothScrollBy(dx, dy)
源码,看看在哪里可以设置滑动速度。
源码中有这一段:

void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator,
            int duration, boolean withNestedScrolling) {
        if (mLayout == null) {
            Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
                    + "Call setLayoutManager with a non-null argument.");
            return;
        }
        if (mLayoutSuppressed) {
            return;
        }
        if (!mLayout.canScrollHorizontally()) {
            dx = 0;
        }
        if (!mLayout.canScrollVertically()) {
            dy = 0;
        }
        if (dx != 0 || dy != 0) {
            boolean durationSuggestsAnimation = duration == UNDEFINED_DURATION || duration > 0;
            if (durationSuggestsAnimation) {
                if (withNestedScrolling) {
                    int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
                    if (dx != 0) {
                        nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
                    }
                    if (dy != 0) {
                        nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
                    }
                    startNestedScroll(nestedScrollAxis, TYPE_NON_TOUCH);
                }
                mViewFlinger.smoothScrollBy(dx, dy, duration, interpolator);
            } else {
                scrollBy(dx, dy);
            }
        }
    }

看注释描述,interpolator是动画插值器,duration是动画时间,withNestedScrolling是嵌套滚动平滑滚动。在上一层传入参数的时候interpolator是null,withNestedScrolling是false。那么我调用这个方法代替之前的

smoothScrollBy(dx, dy)
方法就可以控制滑动速度了。但是这个方法不是public的,不能直接调用,我通过反射取出这个方法:

 			//根据坐标滑动到指定距离
            private void scrollToAmount(RecyclerView recyclerView, int dx, int dy) {
                //如果没有滑动速度等需求,可以直接调用这个方法,使用默认的速度
//                recyclerView.smoothScrollBy(dx,dy);
                //以下对滑动速度提出定制
                try {
                    Class recClass = recyclerView.getClass();
                    Method smoothMethod = recClass.getDeclaredMethod("smoothScrollBy", int.class, int.class, Interpolator.class, int.class);
                    smoothMethod.invoke(recyclerView, dx, dy, new AccelerateDecelerateInterpolator(), 700);//时间设置为700毫秒,
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

之后在item的焦点监听中在调用scrollToAmount就可以了。

全代码:
MainActivity


public class MainActivity extends AppCompatActivity {
    private String[] permissions = new String[]{Manifest.permission.READ_PHONE_STATE, Manifest.permission.CAMERA};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RecyclerView recyclerview = findViewById(R.id.recyclerview);
        recyclerview.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
        Myadapter myadapter = new Myadapter(recyclerview);
        recyclerview.setAdapter(myadapter);
    }
    class Myadapter extends RecyclerView.Adapter{
        private RecyclerView recyclerView;
        public Myadapter(RecyclerView recyclerView){
            this.recyclerView = recyclerView;
        }
        @Override
        public MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View item = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout , parent ,false);
            return new MyHolder(item);
        }
        @Override
        public void onBindViewHolder(@NonNull MyHolder holder, int position) {
        }
        @Override
        public int getItemCount() {
            return 30;
        }
        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 scrollToAmount(RecyclerView recyclerView, int dx, int dy) {
                //如果没有滑动速度等需求,可以直接调用这个方法,使用默认的速度
//                recyclerView.smoothScrollBy(dx,dy);
                //以下对滑动速度提出定制
                try {
                    Class recClass = recyclerView.getClass();
                    Method smoothMethod = recClass.getDeclaredMethod("smoothScrollBy", int.class, int.class, Interpolator.class, int.class);
                    smoothMethod.invoke(recyclerView, dx, dy, new AccelerateDecelerateInterpolator(), 700);//时间设置为700毫秒,
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            
            private int[] getScrollAmount(RecyclerView recyclerView, View view) {
                int[] out = new int[2];
                final int parentLeft = recyclerView.getPaddingLeft();
                final int parentTop = recyclerView.getPaddingTop();
                final int parentRight = recyclerView.getWidth() - recyclerView.getPaddingRight();
                final int childLeft = view.getLeft() + 0 - view.getScrollX();
                final int childTop = view.getTop() + 0 - view.getScrollY();
                final int dx =childLeft - parentLeft - ((parentRight - view.getWidth()) / 2);//item左边距减去Recyclerview不在屏幕内的部分,加当前Recyclerview一半的宽度就是居中
                final int dy = childTop - parentTop - (parentTop - view.getHeight()) / 2;//同上
                out[0] = dx;
                out[1] = dy;
                return out;
            }
        }
        //放大动画
        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();
        }
    }
}

activity_main.xml


item_layout


炭烤葫芦娃 原创文章 11获赞 3访问量 2044 关注 私信 展开阅读全文
作者:炭烤葫芦娃


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

android TV常见需求,焦点item保持居中 —— RecyclerView自定义焦点滑动位置和滑动速度。

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

android TV常见需求,焦点item保持居中 —— RecyclerView自定义焦点滑动位置和滑动速度。

android tv开发和移动端开发最大的不同就是多了一个焦点处理的逻辑。尤其是类似Recyclerview这样本身带有滑动效果,为了醒目的显示当前焦点在什么位置,需要滑动的时候回添加大量的动画、高亮、阴影等效果。同样,让焦点位置不变而列表
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录