Android發光特效焦點框-遙控器版本

適用於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)

 

至此全部完成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章