您的位置:首页 > 移动开发 > Android开发

android自定义View(三)——动画焦点框

2015-07-16 16:01 525 查看
源码已同步到github:https://github.com/ykbjson/FocusView.git

闲话

本来最近在做一个IPTV的广告插件包,因为这个项目没多少功能,仅仅涉及悬浮Window的相关操作,所以后面就很闲,看到别的同事在分析阿狸TV市场的一个炫酷效果,所以就索性自己尝试仿效一下。

模仿效果

这个炫酷的效果大致如下:1.当视图容器里的子视图发生焦点切换时,一个悬浮的焦点框(不是系统的)会从失去焦点的子视图上飘移到获得焦点的子视图上,并且焦点框的大小逐渐变化为获得焦点视图的大小;2,飘移完成后,获得焦点的子视图放大、焦点框放大,失去焦点的子视图缩小。可能我描述起来大家觉得很抽象,有兴趣的童鞋可以去到自家电视上找找(估计没多少机顶盒有阿狸市场),或者运行我的demo,看看基础效果。

过程分析

这里先说明,由于我不是专门负责这个效果的,所以我实现第一个效果后就没有往下继续了,后续效果由别的同事研究,所以以下内容仅围绕第一种效果展开。

估计大部分童鞋都会想到,要实现这个效果,使用的东西要么是动画,要么是矩阵。不错,我是用矩阵也实现了,但是矩阵出现的问题是.9的边框放大后直接画出来也会失真,所以我采用动画。但是可能大部分童鞋都和我一样只是常使用View Animation,我本来也想使用View Animation,但是发现控制焦点框宽高均匀动态变化好难(网上有教使用缩放&平移动画混合并计算动画偏移的方法可以实现这个效果,不过我智商太低,真的看不懂O(∩_∩)O),主要问题在于AnimationListener里没有动画实时更新回调的相关接口,所以我只好另寻出路,于是乎,我认识了Property Animation。关于Property Animation,有兴趣的童鞋请移步:PropertyAnimation详解

实施过程

1.一个自定义ViewGroup

这个容器的作用主要作用是控制焦点框的初始位置和大小以及对子视图焦点变化事件的捕获。

/**
* com.ykbjson.view.MFocusView
*
* @author Kebin.Yan
* @Description 子view获得焦点时焦点框有动画效果的视图容器
* @date Create At :2015年7月2日 上午9:01:37
*/
public class MFocusView extends RelativeLayout implements OnFocusChangeListener, OnClickListener {
private final String TAG = getClass().getSimpleName();
/**
* 焦点框
*/
private FloatView floatView;
/**
* 是否初始化过
*/
private boolean isInit;

public MFocusView(Context context) {
this(context, null);
}

public MFocusView(Context context, AttributeSet attr) {
this(context, attr, 0);
}

public MFocusView(Context context, AttributeSet attr, int style) {
super(context, attr, style);
}

/**
* 初始化焦点框
*/
private void initFloatView()
{
int firstChildwidth = -1;
int firstChildHeight = -1;
//这个地方或许是不对的,因为不一定是第一个视图有焦点
View child = findFocus();
if(null==child)
getChildAt(0);
if (null != child) {
firstChildwidth = (int) (child.getRight() - child.getLeft());
firstChildHeight = (int) (child.getBottom() - child.getTop());
}
floatView = new FloatView(getContext());
LayoutParams floatParams = new LayoutParams(firstChildwidth, firstChildHeight);
floatView.setId(4);
floatView.setLayoutParams(floatParams);
floatView.setFocusable(false);
floatView.setFocusableInTouchMode(false);
floatView.setScaleType(ScaleType.FIT_XY);
floatView.setImageResource(R.drawable.focus_bound);
addView(floatView);
if (null != child)
floatView.onReLayout(child.getLeft(), child.getTop(), firstChildwidth, firstChildHeight);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed && !isInit) {
isInit = !isInit;
if (getChildCount() > 0) {
Log.e(TAG, "onLayout,childCount :  " + getChildCount());
for (int i = 0; i < getChildCount(); i++) {
//加这个是为了在手机上测试
getChildAt(i).setOnClickListener(this);
//捕获子视图焦点
getChildAt(i).setOnFocusChangeListener(this);
}
}
initFloatView();
}
}

@Override
public void onClick(View child) {
onChildChange(child);
}

@Override
public void onFocusChange(View child, boolean hasFocus) {
Log.e(TAG, "onFocusChanged,hasFocus :  " + hasFocus);
if (!hasFocus)
return;
onChildChange(child);
}

/**
* 焦点框视图改变
*/
private void onChildChange() {
View child = findFocus();
onChildChange(child);
}

/**
* 焦点框视图改变
*
* @param child 当前右焦有的视图
*/
private void onChildChange(View child) {
if (null == child)
return;

float focusX = child.getLeft();
float focusY = child.getTop();
float focusW = child.getRight() - focusX;
float focusH = child.getBottom() - focusY;

floatView.onReLayout(focusX, focusY, focusW, focusH);
Log.e(TAG, "onChildChange,child : " + child);
}

}


这个类很简单,就是默认装载焦点框视图到最顶层,捕获到焦点变化后通知焦点框重绘和播放动画。

2.一个ImageView

/**
* com.ykbjson.view.FloatView
*
* @author Kebin.Yan
* @Description 焦点框视图
* @date Create At :2015年7月2日 上午9:02:14
*/
public class FloatView extends ImageView {
private final String TAG = getClass().getSimpleName();
/**
* x轴缩放率
*/
private float scalX = 1.0f;
/**
* y轴缩放率
*/
private float scalY = 1.0f;
/**
* 上一次view的x坐标
*/
public float nFocusX;
/**
* 上一次view的y坐标
*/
public float nFocusY;
/**
* 上一次view的宽度
*/
public int nFocusW;
/**
* 上一次view的高度
*/
public int nFocusH;

public FloatView(Context context) {
super(context);
}

/**
* 改变当前视图位置和大小
*
* @param focusX 新的x坐标
* @param focusY 新的y坐标
* @param focusW 新的宽度
* @param focusH 新的高度
*/
public void onReLayout(final float focusX, final float focusY, final float focusW, final float focusH) {
scalX = focusW / getWidth();
scalY = focusH / getHeight();

ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setObjectValues(new LayoutParams(nFocusW, nFocusH));
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setEvaluator(new TypeEvaluator<LayoutParams>() {
@Override
public LayoutParams evaluate(float fraction, LayoutParams startValue, LayoutParams endValue) {
Log.e(TAG, "evaluate , fraction = " + fraction);
LayoutParams params = new LayoutParams(0, 0);
params.width = (int) ((focusW - getWidth()) * fraction);
params.height = (int) ((focusH - getHeight()) * fraction);
return params;
}
});

valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
LayoutParams nParams = (LayoutParams) animation.getAnimatedValue();
LayoutParams params = (LayoutParams) getLayoutParams();
params.width += nParams.width;
params.height += nParams.height;
setLayoutParams(params);
}
});

valueAnimator.setStartDelay(250);
AnimatorSet set = new AnimatorSet();
set.playTogether(ObjectAnimator.ofFloat(this, "translationX", nFocusX, focusX), ObjectAnimator.ofFloat(this, "translationY", nFocusY, focusY), valueAnimator);
set.setDuration(500);
set.setTarget(this);
set.start();

nFocusX = focusX;
nFocusY = focusY;
nFocusW = (int) focusW;
nFocusH = (int) focusH;
}

@Override
protected void onDraw(Canvas canvas) {
Log.e(TAG, "onDraw " + " scalX : " + scalX + " scalY : " + scalY + " nFocusX : " + nFocusX + " nFocusY : " + nFocusY);
super.onDraw(canvas);
}
}


这个ImageView主要是实现焦点框(它本身)的平移和缩放,需要看的地方就是AnimatorUpdateListener那里,我就不再赘述了。

结语

存在的bug:

1.在多点触控手机上同时按两个按钮时动画混乱。

2.如果子视图不自定义selector,点击时系统原生的焦点框会先于这个飘移框出现

其实每次写文章不是为了贴代码,是希望弄清楚一些原理,更希望提升自己的语言组织能力和表达能力。

我相信,肯定有数不尽的大牛有很多的实现方式,但我依然要写出来,因为我希望让更多还未成为大牛的人得到一个思路。在浩瀚的Android攻城狮队伍中,我不过如那银河里的一粒星辰,即使渺小,但依然努力发光。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: