TV 游标动画浅析

作者:夏至 欢饮转载,也请保留这段申明
http://blog.csdn.net/u011418943/article/details/64907449

注: 本文参考为 酷莓科技的demo,git为:https://git.oschina.net/kumei/AndroidTVWidget
tv上非常不错的框架,这里的游标动画也是参考它的,只不过去掉一些多余的代码,保留动画部分;

    本文demo的git地址为: http://git.oschina.net/zhengshaorui/AndroidTVAnimationDemo

要说明的一点是,Android TV 不同于手机,由于TV 是用遥控来控制移动方向的,所以,在移动的过程中,怎么让用户知道它的操作;常见的有在选中控件的时候,添加它的北京框;或者就是直接放大,但这样的效果往往看起来很生硬,用户体验不好;
而市面上越来越多采用移动的游标来提示用户的操作,那么看起来很炫酷的游标是怎么形成的呢?先看Demo的效果图(gif 图效果不太好):
这里写图片描述

首先先来分析一下;
1、

  • 控件必须满足focus的功能,所以,我们需要在控件添加
    android:focusableInTouchMode=”true” 和 android:clickable=”true”。
  • 选中的时候,要添加放大的效果;用属性动画即可,且要重新它的父布局,这样控件放大了,才不会被其他控件挡住;
  • 移动的边框,这个看起来比较难理解,其实,我们只要获取第一个控件的left 和top,与第二个控件的left和top相减,得到它移动的距离即可

    一、布局的实现

    首先,我们需要先定义一个最上层的 layout ,用来包裹我们所有的控件,这样,主要是为了放大的时候,控件不会被挡住;
    MainUplayout.java 如下

/**
 * 最上层包裹的类
 * @author zhengshaorui
 */
public class MainUpLayout extends RelativeLayout{
    private int position;
    public MainUpLayout(Context context) {
        super(context,null);
        // TODO Auto-generated constructor stub
    }
    public MainUpLayout(Context context, AttributeSet attrs) {
        super(context, attrs,0);
        init(context);
        // TODO Auto-generated constructor stub
    }
    public MainUpLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
    }

    private void init(Context context){
        setClipChildren(false); //是否现限制其他控件在它周围绘制,这里我们要绘制边框,所以选择false
        setClipToPadding(false); //是否限制控件区域在padding里面,与上面的属性一起使用
        setChildrenDrawingOrderEnabled(true);//用于改变控件的绘制顺序,由于可能用到放大的空间,所以这里需要改变一下
        // 获取焦点,重绘item,防止控件放大被挡住的问题,但是问题是绘制频繁,会导致卡顿,不建议用
        // 最好的办法是看哪一个被挡住了,获取id,然后让控件重画,这样会好点。
        getViewTreeObserver()
        .addOnGlobalFocusChangeListener(new OnGlobalFocusChangeListener() {
            @Override
            public void onGlobalFocusChanged(View oldFocus, View newFocus) {
                position = indexOfChild(newFocus);
                if (position != -1) {
                    postInvalidate();
                }
            }
        });
    }
    @Override
    protected int getChildDrawingOrder(int childCount, int i) {
        if (position != -1) {
            if (i == childCount - 1){
                return position;
            }
            if (i == position) //此为选中的,当让它最后一个绘画,倒叙
                return childCount - 1; 
        }
        return i;

    }
}

注意到我们用到了两个方法:

setClipChildren(false); //是否现限制其他控件在它周围绘制,这里我们要绘制边框,所以选择false
setClipToPadding(false); //是否限制控件区域在padding里面,与上面的属性一起使用

控件在放大和添加阴影的时候,需要在周围绘制区域,所以,我们需要用到上面的两个属性;

然后,先看布局,activity_main.xml :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    android:clipToPadding="false"
    tools:context="com.example.rectanimationtest.MainActivity" >
    <com.example.rectanimationtest.AnimaUtils.MainUpLayout
        android:id="@+id/mainuplayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
         >
        <ImageView
            android:id="@+id/image"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:clickable="true"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:src="@drawable/square" />

        <ImageView
            android:id="@+id/image1"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_toRightOf="@+id/image"
            android:clickable="true"
            android:layout_marginLeft="3dp"
            android:focusableInTouchMode="true"
            android:src="@drawable/square" />

        <ImageView
            android:id="@+id/image2"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_toRightOf="@+id/image1"
            android:clickable="true"
            android:focusable="true"
            android:layout_marginLeft="3dp"
            android:focusableInTouchMode="true"
            android:src="@drawable/square" />
        <ImageView
            android:id="@+id/rect"
            android:layout_width="100dp"
            android:layout_height="200dp"
            android:clickable="true"
            android:focusable="true"
            android:scaleType="fitXY"
            android:focusableInTouchMode="true"
            android:layout_marginLeft="3dp"
            android:layout_toRightOf="@+id/image2"
            android:src="@drawable/square" />
    </com.example.rectanimationtest.AnimaUtils.MainUpLayout>

    <com.example.rectanimationtest.AnimaUtils.MainUpView
        android:id="@+id/mainview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
</RelativeLayout>

二、放大效果

可以看到,我们最主要的绘制函数就是 MainUpView这个类了,这个类就是我们的边框移动 View,这个 View 主要实现边框的生成与移动,还有阴影的添加;这个我们暂时先不看,我们先实现放大的功能,主函数 MainActivity.class这样写:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        mainUpView = (MainUpView) findViewById(R.id.mainview);
        mainUpView.setUpRectResource(R.drawable.test_rectangle);//设置边框
        mainUpView.setUpRectShadeResource(R.drawable.item_shadow);//设置阴影
        MainUpLayout mainUpLayout = (MainUpLayout) findViewById(R.id.mainuplayout);
        mainUpLayout.getViewTreeObserver().addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {
            @Override
            public void onGlobalFocusChanged(View oldView, View currentView) {
                mainUpView.setFocusView(currentView,oldView,SACLE);
                oldView = currentView;  //4.3以下需手动保存

            }
        });
    }

可以看到,这里用到了 onGlobalFocusChangeListener 来监听,MainUpLayout 布局下的控件,然后把我们获取的 View,传给 MainUpView ,在里面实现相应逻辑,所以,放大的逻辑为:

if (currentView != null) {
        currentView.animate().scaleX(scale).scaleY(scale).setDuration(ANIMATION_TIME).start();
        if (oldView != null) {
        oldView.animate().scaleX(DEFUALT_SCALE).scaleY(DEFUALT_SCALE).setDuration(ANIMATION_TIME).start();
        }
    }

非常简单,直接用属性动画就可以搞定,效果图如下:
这里写图片描述

三、移动边框函数的实现

由于被我封装好了,直接贴上 MainUpView 反而让大家看不懂,所以还是直接上 移动边框的实现函数吧:

当然,工程已经上传了: http://git.oschina.net/zhengshaorui/AndroidTVAnimationDemo

public void rectMoveAnimation(View focusView,float scaleX,float scaleY){
        Rect fromRect = findLocationWithView(getMainUpView());
        Rect toRect = findLocationWithView(focusView);
        int disX = toRect.left - fromRect.left;
        int disY = toRect.top - fromRect.top;
        rectMoveMainLogic(focusView,disX,disY,scaleX,scaleY);
    }
    private void rectMoveMainLogic(View focusView, float x, float y,float scaleX,float scaleY)
    {
        int newWidth = 0;
        int newHeight = 0;
        int oldWidth = 0;
        int oldHeight = 0;
        if (focusView != null) {
            newWidth = (int) (focusView.getMeasuredWidth() * scaleX);
            newHeight = (int) (focusView.getMeasuredHeight() * scaleY);
            x = x + (focusView.getMeasuredWidth() - newWidth) / 2;
            y = y + (focusView.getMeasuredHeight() - newHeight) / 2;
        }
        // 取消之前的动画.
        if (mCurrentAnimatorSet != null)
            mCurrentAnimatorSet.cancel();
        oldWidth = getMainUpView().getMeasuredWidth();
        oldHeight = getMainUpView().getMeasuredHeight();
        ObjectAnimator transAnimatorX = ObjectAnimator.ofFloat(getMainUpView(), "translationX", x);
        ObjectAnimator transAnimatorY = ObjectAnimator.ofFloat(getMainUpView(), "translationY", y);
        ObjectAnimator scaleXAnimator = ObjectAnimator.ofInt(new ScaleView(getMainUpView()), "width", oldWidth,
                (int) newWidth);
        ObjectAnimator scaleYAnimator = ObjectAnimator.ofInt(new ScaleView(getMainUpView()), "height", oldHeight,
                (int) newHeight);
        //
        AnimatorSet mAnimatorSet = new AnimatorSet();
        mAnimatorSet.playTogether(transAnimatorX, transAnimatorY, scaleXAnimator, scaleYAnimator);
        mAnimatorSet.setInterpolator(new DecelerateInterpolator(1));
        mAnimatorSet.setDuration(ANIMATION_TIME);
        getMainUpView().setVisibility(View.VISIBLE);
        mAnimatorSet.start();
        mCurrentAnimatorSet = mAnimatorSet;
    }

实现的原理是,或者两个控件之间的移动距离,所以只需要 left 和 top即可,接着就是用属性动画,边框从当前控件移动到另一个控件上就可以了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值