Android 視訊控制器出入邏輯及動畫的封裝
最近有朋友在做視訊播放器,跟我提到了在做控制器的時候感覺邏輯和動畫有一定嗯複雜性,讓我一下子有了興趣。
下圖為實現的效果:
做這麼一個功能,要考慮的內容大概分為以下幾種:
1.介面上能看到的檢視;
2.動畫效果的實現;
3.控制器的收起、彈出相關的控制邏輯。
分析:
1.demo 中雖然只有上下兩個控制器,但實際應用中也許左右也有,甚至中間也有,但無論如何他們都有一個共性:彈出、收起;
2.我朋友遇到的困難大概在於他所使用的 view 動畫在頻繁的點選事件下很難保證流暢性與連續性。並且熟悉 Android 動畫的朋友應該知道,view 動畫不會真正移動 view 的位置,也就是說他不得不對動畫加上監聽,去動態控制 view 上子 view 的點選事件是否有效。綜上,在此處採用屬性動畫應該會更合適;
3.在 demo 中點選一次螢幕會彈出控制器,再點選會收起,彈出兩秒左右之後還會自動收起。並且在動畫執行過程中再次點選,會取消當前動畫並反向執行。這個邏輯也許會根據業務的需求發生變化,所以應當儘可能只寫在一處。
分享一下我實現的程式碼:
/**
* Controller 的定義
* 就是說你至少得可以 顯示/隱藏 才能稱為 Controller 對吧?
*/
public interface Controller {
void show();
void hide();
}
/** * 採用 ValueAnimator 實現動畫效果的基類 Controller */ public abstract class ValueAnimatorController implements Controller { private static final int DURATION = 250; /** * 子類提供顯示時的目標 value * @return */ protected abstract int getShowTarget(); /** * 子類提供隱藏時的目標 value * @return */ protected abstract int getHideTarget(); /** * 子類處理動態計算出的 value 以實現動畫效果 * @param shift */ protected abstract void onShiftChanged(int shift); protected View view; protected ValueAnimator va; protected int shift; public ValueAnimatorController(View view) { this.view = view; } @Override public void show() { stop(); makeAnimation(getShowTarget()); } @Override public void hide() { stop(); makeAnimation(getHideTarget()); } private void stop() { if (va != null) { va.cancel(); } } protected void makeAnimation(int target) { //這裡採用當前狀態的 shift 而不是初始值, //是為了動畫被停止後,朝反方向移動更平滑 va = ValueAnimator.ofInt(shift, target); va.setDuration(DURATION); va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { shift = (int) animation.getAnimatedValue(); onShiftChanged(shift); } }); va.start(); } }
以上程式碼分別對應問題1、2、3。/** * 出入事件分發及邏輯控制 */ public class ControllerManager { /** * true 當前處於顯示狀態,或正在執行顯示動畫 * false 當前處於隱藏狀態,或正在執行隱藏動畫 */ private boolean showing = true; private List<Controller> controllerList = new ArrayList<>(); private CountDownTimer countDownTimer; public ControllerManager addController(Controller controller) { controllerList.add(controller); return this; } /** * 初始化完成後 開始倒計時隱藏 controllers * @return */ public ControllerManager startWorking() { startCount(); return this; } public boolean isShowing() { return showing; } /** * 切換狀態 * 同時取消倒計時 */ public void switchState() { stopCount(); showing = !showing; if (showing) { show(); } else { hide(); } } /** * 分發 show 事件至所有 controller * 同時開始倒計時 */ private void show() { for (Controller controller : controllerList) { controller.show(); } startCount(); } private void hide() { for (Controller controller : controllerList) { controller.hide(); } } /** * 倒計時結束時切換狀態 */ private void startCount() { countDownTimer = new CountDownTimer(2500, 2500) { @Override public void onTick(long millisUntilFinished) { } @Override public void onFinish() { switchState(); } }.start(); } private void stopCount() { if (countDownTimer != null) { countDownTimer.cancel(); countDownTimer = null; } } }
實際使用方法如下:
1.先繼承 ValueAnimatorController 實現 Top 和 Bottom 兩種動畫
public class TopController extends ValueAnimatorController {
public TopController(View view) {
super(view);
}
@Override
protected int getShowTarget() {
return 0;
}
@Override
protected int getHideTarget() {
return view.getHeight();
}
@Override
protected void onShiftChanged(int shift) {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams();
params.setMargins(params.leftMargin, -shift, params.rightMargin, params.bottomMargin);
view.setLayoutParams(params);
}
}
public class BottomController extends ValueAnimatorController {
public BottomController(View view) {
super(view);
}
@Override
protected int getShowTarget() {
return 0;
}
@Override
protected int getHideTarget() {
return view.getHeight();
}
@Override
protected void onShiftChanged(int shift) {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams();
params.setMargins(params.leftMargin, params.topMargin, params.rightMargin, -shift);
view.setLayoutParams(params);
}
}
2.將實際的 view 傳入至 controller 中,再將 controller 統一交由 ControllerManager 統一處理事件
public class ControllersView extends RelativeLayout {
private Context context;
private View topView;
private View bottomView;
private ControllerManager controllerManager;
public ControllersView(Context context) {
super(context);
this.context = context;
initView();
}
private void initView() {
View rootView = LayoutInflater.from(context).inflate(R.layout.layout_control, this);
topView = rootView.findViewById(R.id.ll_top);
bottomView = rootView.findViewById(R.id.rl_bottom);
controllerManager = new ControllerManager();
controllerManager.addController(new TopController(topView))
.addController(new BottomController(bottomView))
.startWorking();
rootView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
controllerManager.switchState();
}
});
}
}
3.demo 中寫得較為簡單,直接將上面的 ControllersView 新增至了 Activity 中
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new ControllersView(this));
}
}
擴充套件思路:
1.假如介面正中會出現一個按鈕,出現時一邊旋轉一邊變大,消失時反之,則在 ValueAnimator 的基礎上新建一個 CenterController,重寫 getShowTarget, getHideTarget, onShiftChange 三個方法,再將按鈕的 view 和此 controller 繫結再提交至 controllerManager 即可;
2.如果介面上有多種不同的邏輯出現,比如說上述1中按鈕需要雙擊才能消失,那麼需要重寫一種 ControllerManager,實現不同的邏輯。即在自定義的View中,根據邏輯的不同,會同時存在多個不同的 ControllerManager,用來管理邏輯不同的 view;
3.如果不想使用 ValueAnimator 來實現動畫效果,可以自行寫一個實現了 Controller 介面所描述方法的類。
專案原始碼: