适用于android智能电视的全局焦点框控件,可以省去为每个按钮设计focused的按钮图标。
效果描述:
1.完整适配各种尺寸的图标按钮
2.平滑的补件动画切换焦点
3.点击效果闪光+声音
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationSet;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
/**
* 焦点框控件
* @author jiangyuchen
* @date 2014-2-13
*/
public class BorderView extends ImageView implements AnimationListener {
protected static final String TAG = "BestSetting.BorderView";
private static int BORDER_SIZE = 20;
private static int TRAN_DUR_ANIM = 250;
private SoundPool sp;
private Context mContext;
public BorderView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
}
/**
* 设置边界框的外框大小
* @param padding
*/
public void setBorderSize(int size){
BORDER_SIZE = size;
}
/**
* 设置位移动画时间
* @param dur
*/
public void setTranslateAnimtionDuration(int dur){
TRAN_DUR_ANIM = dur;
}
public void setLocation(View view){
ViewLocation location = findLocationWithView(view);
// Log.v(TAG, "setLocation X:"+location.x+" Y:"+location.y);
mLeft = location.x-(int)BORDER_SIZE;
mTop = location.y-(int)BORDER_SIZE;
mRight = location.x+(int)BORDER_SIZE+view.getWidth();
mBottom = location.y+(int)BORDER_SIZE+view.getHeight();
this.layout(mLeft, mTop, mRight, mBottom);
this.clearAnimation();
BorderView.this.setVisibility(View.VISIBLE);
}
private int mLeft, mTop, mRight, mBottom;
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
if(this.mLeft != left || mTop != top || mRight != right || mBottom != bottom){
this.layout(this.mLeft, this.mTop, this.mRight, this.mBottom);
}
}
/**
* 获取View的位置
* @param view 获取的控件
* @return 位置
*/
public ViewLocation findLocationWithView(View view){
int[] location = new int[2];
view.getLocationOnScreen(location);
return new ViewLocation(location[0], location[1]);
}
private AnimationDrawable mBoxBgAnim;
/**
* 初始化焦点框动画
*/
public void runBorderAnimation(){
this.setBackgroundResource(R.anim.box_normal);
restartBoxAnim();
}
/**
* 重启闪烁动画
* @param context
*/
public void restartBoxAnim(){
BorderView.this.setVisibility(View.VISIBLE);
this.clearAnimation();
if(mBoxBgAnim == null){
mBoxBgAnim = (AnimationDrawable) this.getBackground();
}
if(mBoxBgAnim.isRunning()){
mBoxBgAnim.stop();
}
mBoxBgAnim.start();
this.startAnimation(AnimUtils.buildAnimBoxNormal(mContext));
}
/**
* 记录上一次的焦点组件,用于判断是否未移动控件的焦点,相同则不重新加载动画
*/
private View mLastFocusView;
/**
* 启动焦点框位移动画
*/
public void runTranslateAnimation(View toView) {
runBorderAnimation();
if(toView == null || mLastFocusView == toView){
return;
}
//缩放比例
float scaleWValue = (float) this.getWidth()/((float) toView.getWidth()+2*BORDER_SIZE);
float scaleHValue = (float) this.getHeight()/((float) toView.getHeight()+2*BORDER_SIZE);
ScaleAnimation scale = new ScaleAnimation( scaleWValue, 1.0f,
scaleHValue,1.0f);
//记录位置信息,以为启动动画前box已经设置到目标位置了。
ViewLocation fromLocation = findLocationWithView(this);
ViewLocation toLocation = findLocationWithView(toView);
TranslateAnimation tran = new TranslateAnimation(-toLocation.x+(float)BORDER_SIZE+fromLocation.x, 0,
-toLocation.y+(float)BORDER_SIZE+fromLocation.y, 0);
// Log.v("TAG","fromX:"+(-toLocation.x+(float)BORDER_SIZE+fromLocation.x)+" fromY:"+(-toLocation.y+(float)BORDER_SIZE+fromLocation.y));
// Log.v("TAG","fromX:"+fromLocation.x+ " toX:" +toLocation.x+" fromY:"+fromLocation.y+" toY:"+toLocation.x);
// TranslateAnimation tran = new TranslateAnimation(0, toLocation.x-(float)BORDER_SIZE-fromLocation.x,
// 0, toLocation.y-(float)BORDER_SIZE-fromLocation.y);
AnimationSet boxAnimaSet = new AnimationSet(true);
boxAnimaSet.setAnimationListener(this);
boxAnimaSet.addAnimation(scale);
boxAnimaSet.addAnimation(tran);
boxAnimaSet.setDuration(TRAN_DUR_ANIM);
BorderView.this.setVisibility(View.INVISIBLE);
setLocation(toView);//先位移到目标位置再启动动画
Log.v(TAG, "setLocation runTranslateAnimation");
BorderView.this.startAnimation(boxAnimaSet);
mLastFocusView = toView;
}
public void playClickOgg(){
if(sp == null){
sp =new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
sp.load(mContext, R.raw.djtx, 0);
}
sp.play(1, 1, 1, 0, 0, 1);
}
private static AnimationSet mBoxAnimClick;
private void runClickAnimtion(){
playClickOgg();
if(mBoxAnimClick == null){
mBoxAnimClick = AnimUtils.buildAnimBoxClick(mContext);
}
BorderView.this.startAnimation(mBoxAnimClick);
notifyRestartBoxAnim(500);
}
public static final int MSG_BOX_BG_ANIM = 10;
public static final int MSG_BOX_CLICK_ANIM = 11;
/**
* 重启背景动画
* @param delay 延迟时间毫秒
*/
void notifyRestartBoxAnim(int delay){
mBoxHandler.sendEmptyMessageDelayed(MSG_BOX_BG_ANIM, delay);
}
/**
* 点击动画
*/
public void notifyClickBoxAnim(){
mBoxHandler.sendEmptyMessageDelayed(MSG_BOX_CLICK_ANIM, 10);
}
Handler mBoxHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch(msg.what){
case MSG_BOX_BG_ANIM:
restartBoxAnim();
break;
case MSG_BOX_CLICK_ANIM:
runClickAnimtion();
break;
}
};
};
@Override
public void onAnimationEnd(Animation arg0) {
notifyRestartBoxAnim(0);
}
@Override
public void onAnimationRepeat(Animation arg0) {
}
@Override
public void onAnimationStart(Animation arg0) {
}
}
动画类:
import android.content.Context;
import android.util.Log;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.ScaleAnimation;
public class AnimUtils {
private static final String TAG = "AnimUtils";
private static Animation mBoxAnimNormal;
public static Animation buildAnimBoxNormal(Context context) {
if (mBoxAnimNormal != null) {
return mBoxAnimNormal;
}
mBoxAnimNormal = android.view.animation.AnimationUtils.loadAnimation(
context, R.anim.box_alpha);
return mBoxAnimNormal;
}
public static AnimationSet buildAnimBoxClick(Context context) {
final ScaleAnimation scale = new ScaleAnimation(0.5f, 1.3f, 0.5f, 1.3f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
AlphaAnimation alpha = new AlphaAnimation(0.5f, 1.0f);
AnimationSet mBoxAnimClick = new AnimationSet(true);
mBoxAnimClick.addAnimation(scale);
mBoxAnimClick.addAnimation(alpha);
mBoxAnimClick.setDuration(100);
return mBoxAnimClick;
}
}
动画xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
>
<alpha
android:repeatCount="infinite"
android:repeatMode="restart"
android:duration="1000"
android:fromAlpha="2.0"
android:toAlpha="0.1" />
<alpha
android:repeatCount="infinite"
android:repeatMode="restart"
android:duration="1000"
android:fromAlpha="0.1"
android:toAlpha="2.0" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false" >
<item
android:drawable="@drawable/bound"
android:duration="1000"/>
<item
android:drawable="@drawable/bound2"
android:duration="1000"/>
</animation-list>
bound:
bound2:
加入到布局中方法:
父布局使用相对布局
<com.bestv.setting.views.BorderView
android:layout_width="0px"
android:layout_height="0px"
android:id="@+id/box"
/>
在Acitivity中的使用方法:
import android.app.Activity;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import com.bestv.setting.utils.BoxNotFoundException;
import com.bestv.setting.views.BorderView;
public class BaseActivity extends Activity implements OnFocusChangeListener {
private static final String TAG_base = "BaseActivity";
BorderView mBorderView;
Handler mHandler = new Handler();
@Override
protected void onStart() {
super.onStart();
View view = findViewById(R.id.box);
if(view == null){
throw new BoxNotFoundException();//必须在父布局中焦点框控件,否则抛出异常
}
}
public BorderView getBorderView() {
return mBorderView;
}
public void setBorderView(BorderView box) {
this.mBorderView = box;
}
public void setFocusedView(final View view, int delay){
if(mBorderView == null){
mBorderView = (BorderView)findViewById(R.id.box);
}
mBorderView.runBorderAnimation();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if(view == null){
return;
}
view.requestFocus();
mBorderView.setLocation(view);
}
}, delay);
}
public void runClickAnim(){
this.getBorderView().notifyClickBoxAnim();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
if(hasFocus){
mBorderView.runTranslateAnimation(v);
}
}
public void setClickListener(View v, OnClickListener listener){
v.setOnClickListener(listener);
v.setOnFocusChangeListener(this);
}
}
为每个能够获取到焦点的空间调用函数:
setClickListener(View v, OnClickListener listener)
至此全部完成。