AndroidTV开发7实现仿小米电视和各种盒子TV焦点放大缩小效果

AndroidTV中最常见的就是焦点效果,这个实现很简单,在代码设置view.setFocusable(true),布局中focusable="true",然后添加view获得焦点时的效果即可。但是本文不讲这些,而是讲解View获取焦点有一个放大缩小效果,当view放大时处于临近的View之上并且不能被遮挡,当view缩小时要恢复原来的大小。

 

1.先放几张效果图:

2.方案分析,网上的方案和资料如下:

(1):MainUpView:

(2):MetroItemFrameLayout(何俊林大神的方案):博客地址:Android TV开发总结(一)构建一个TV app前要知道的事儿_欢迎关注公众号:【码农突围】,公号后台回复9999,可以获取一份500页的LeetCode刷题笔记。-CSDN博客

(3)Android tv metro,项目地址:android_tv_metro: android tv metro widget 源码分析

(4)Android tv frame,项目地址:Android-TV-Frame-new: Android TV开发框架 最新版本.

(5)TvRecyclerView,项目地址:https://github.com/henryblue/TvRecyclerView

(6)TvRecyclerView,项目地址:https://github.com/zhousuqiang/TvRecyclerView

(7)TVRecyclerView,项目地址:GitHub - Fredlxy/TVRecyclerView: 运行在Android TV上的RecyclerView

(8)Android tv frame,项目地址:GitHub - songwenju/CustomTvRecyclerView

(9)TvWidget,项目地址:GitHub - evilbinary/TvWidget: tv常用效果控件,包括焦点、边框处理等。

(10)BorderViewDemo,项目地址:https://github.com/lf8289/BorderViewDemo

(11)TvFocusBorder,项目地址:https://github.com/zhousuqiang/TvFocusBorder

(12) Android Tv Widget Demo,项目地址: https://github.com/zhousuqiang/TvWidget

(13)SMTVLauncher,项目地址:GitHub - FrozenFreeFall/SMTVLauncher: 神马视频是一款包含直播、回看、点播、设置于一体的TVLauncher

(14)AndroidTVLauncher,项目地址:GitHub - JackyAndroid/AndroidTVLauncher: This is a leanback style tv launcher(minSdkVersion 17)

(15)TVSample,项目地址:GitHub - smartyuge/TVSample: 1、仿泰捷视频最新TV版 Metro UI效果. 2、仿腾讯视频TV版(云视听·极光) 列表页

(16)Leanback桌面demo,项目地址:https://gitee.com/chuangshiji/Launcher

3.以上方案尝试了一半,由于时间和项目周期问题,所以有些没来得及尝试,而且接入成本有点大,每个项目都不一样,选择适合自己的才最重要,我们不要拘泥于某一种方案或者第三方库.这里我根据以上方案总结出一份属于我自己的方案,主要代码如下:

 

4.Fragment完整代码如下:

package com.example.tvrecyclerview.fragment;

import android.os.Build;
import android.view.View;
import android.widget.ImageView;

import com.example.tvrecyclerview.R;

/**
 * @author: njb
 * @date: 2020/9/24 0024 0:47
 * @desc:
 */
public class OtherFragment extends FragmentBase implements View.OnFocusChangeListener {
    private static final float scale0 = 1f;
    private static final float scaleL = 1.12f;
    private static final float scaleS = 1.22f;
    private static final float z1 = 0f;
    private static final float z2 = 10f;
    private static final long animateDuration = 20;
    private static View lastFocus;
    private ImageView ivRecentControl, ivCollectControl, ivRemoteControl, ivHandleControl, ivQrCodeControl;

    @Override
    protected int getLayoutRes() {
        return R.layout.fragment_other;
    }

    @Override
    protected void rootViewNoNull() {
        super.rootViewNoNull();
        if(null != lastFocus){
            lastFocus.requestFocus();
        }
    }

    @Override
    protected void initView(View rootView) {
        super.initView(rootView);
        //初始化控件
        ivRecentControl = rootView.findViewById(R.id.iv_my_recent);
        ivCollectControl = rootView.findViewById(R.id.iv_my_collect);
        ivHandleControl = rootView.findViewById(R.id.iv_my_handle);
        ivRemoteControl = rootView.findViewById(R.id.iv_my_remote);
        ivQrCodeControl = rootView.findViewById(R.id.iv_my_qrcode);
        //初始化焦点事件
        ivRecentControl.setOnFocusChangeListener(this);
        ivCollectControl.setOnFocusChangeListener(this);
        ivRemoteControl.setOnFocusChangeListener(this);
        ivHandleControl.setOnFocusChangeListener(this);
        ivQrCodeControl.setOnFocusChangeListener(this);
    }

    @Override
    public void onFocusChange(final View v, boolean hasFocus) {
        //初始化放大倍数
        float scaleX1, scaleX2, scaleY1, scaleY2;
        scaleX1 = scaleY1 = scaleX2 = scaleY2 = scale0;
        //给获取焦点的view设置倍数
        if (v.getId() == R.id.iv_my_recent || v.getId() == R.id.iv_my_collect) {
            scaleX2 = scaleY2 = scaleL;
        }
        if (v.getId() == R.id.iv_my_handle || v.getId() == R.id.iv_my_remote || v.getId() == R.id.iv_my_qrcode) {
            scaleX2 = scaleY2 = scaleS;
        }
        //获得焦点时的状态和倍数
        if (hasFocus) {
            lastFocus = v;
            //在android5.0以上的效果
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                v.animate()
                        .scaleXBy(scaleX1)
                        .scaleX(scaleX2)
                        .scaleYBy(scaleY1)
                        .scaleY(scaleY2)
                        .zBy(z1)
                        .z(z2)
                        .setDuration(animateDuration);
            } else {
                //在android5.0以下的效果
                v.animate()
                        .scaleXBy(scaleX1)
                        .scaleX(scaleX2)
                        .scaleYBy(scaleY1)
                        .scaleY(scaleY2)
                        .withEndAction(new Runnable() {
                            @Override
                            public void run() {
                                //防止view被遮挡
                                v.bringToFront();
                            }
                        })
                        .setDuration(animateDuration);
            }
        } else {
            //view在焦点消失后的倍数和状态
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                v.animate()
                        .scaleXBy(scaleX2)
                        .scaleX(scaleX1)
                        .scaleYBy(scaleY2)
                        .scaleY(scaleY1)
                        .zBy(z2)
                        .z(z1)
                        .setDuration(animateDuration);
            } else {
                v.animate()
                        .scaleXBy(scaleX2)
                        .scaleX(scaleX1)
                        .scaleYBy(scaleY2)
                        .scaleY(scaleY1)
                        .setDuration(animateDuration);
            }
        }
    }
}

5.布局文件fragment_other.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/color_blue">

   <include
       layout="@layout/layout_more_game"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

6.layout_more_game.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/cl_more"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="20dp"
    android:layout_marginTop="16dp"
    android:clipChildren="false"
    android:clipToPadding="false"
    tools:ignore="MissingConstraints">


    <ImageView
        android:id="@+id/iv_my_recent"
        android:layout_width="91dp"
        android:layout_height="111dp"
        android:background="@drawable/bg_more_focus"
        android:focusable="true"
        android:padding="2dp"
        android:scaleType="fitXY"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:src="@mipmap/ic_recent"
        android:layout_marginEnd="16dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/iv_my_collect"/>


    <ImageView
        android:id="@+id/iv_my_collect"
        android:layout_width="91dp"
        android:layout_height="111dp"
        android:layout_marginEnd="16dp"
        android:background="@drawable/bg_more_focus"
        android:focusable="true"
        android:padding="2dp"
        android:scaleType="fitXY"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:src="@mipmap/ic_collect"
        app:layout_constraintLeft_toRightOf="@+id/iv_my_recent"
        app:layout_constraintRight_toLeftOf="@id/iv_my_handle"/>

    <ImageView
        android:id="@+id/iv_my_handle"
        android:layout_width="0dp"
        android:layout_height="111dp"
        android:layout_marginEnd="16dp"
        android:background="@drawable/bg_more_focus"
        android:focusable="true"
        android:padding="2dp"
        android:scaleType="fitXY"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:src="@mipmap/ic_handle_game"
        app:layout_constraintLeft_toRightOf="@+id/iv_my_collect"
        app:layout_constraintRight_toLeftOf="@+id/iv_my_remote"/>


    <ImageView
        android:id="@+id/iv_my_remote"
        android:layout_width="0dp"
        android:layout_height="111dp"
        android:background="@drawable/bg_more_focus"
        android:focusable="true"
        android:padding="2dp"
        android:scaleType="fitXY"
        android:layout_marginEnd="16dp"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:src="@mipmap/ic_remote_control"
        app:layout_constraintLeft_toRightOf="@+id/iv_my_handle"
        app:layout_constraintRight_toLeftOf="@+id/iv_my_qrcode"/>

    <ImageView
        android:id="@+id/iv_my_qrcode"
        android:layout_width="0dp"
        android:layout_height="111dp"
        android:background="@drawable/bg_more_focus"
        android:focusable="true"
        android:padding="2dp"
        android:scaleType="fitXY"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:src="@mipmap/ic_qrcode_control"
        app:layout_constraintLeft_toRightOf="@+id/iv_my_remote"
        app:layout_constraintRight_toRightOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

7.实现的效果图如下:

8.简单分析:要想 实现以上效果需要考虑几个问题:

a.放大后的倍数

b.缩小后的倍数

c.view处于临界view之上的边界被切问题

d.在android5.0以上和5.0以下的适配问题

e.封装成一个工具类,因为现在的界面基本上都很复杂,如果是多个布局需要写多个view和倍数等,这样的话每个界面都要写很多次,感觉都是做重复的工作。

9.以上就是TV盒子上的焦点效果实现过程和原理简单分析,后面会出一篇博客详细分析怎么计算倍数,下一篇将会分析怎么封装成一个工具类和封装遇到的问题,欢迎小伙伴们前来讨论留言,有错误指正出来,我会及时更改。

项目的源码地址如下:TvRecyclerView: TV项目,使用recyclerview实现标题切换和焦点效果

おすすめ

転載: blog.csdn.net/u012556114/article/details/108860875