作者:夏至 欢饮转载,也请保留这段申明
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即可,接着就是用属性动画,边框从当前控件移动到另一个控件上就可以了。