您好,登錄后才能下訂單哦!
這篇文章主要講解了“怎么實現Android TV 3D卡片無限循環效果”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“怎么實現Android TV 3D卡片無限循環效果”吧!
自定義View + 屬性動畫ObjectAnimator
按鍵事件特殊處理。
1、ObjectAnimator屬性動畫的知識準備。
2、父不居中自定義ScheduleView,View2, View3
<com.base.module.gvclauncher2.ui.ScheduleView android:id="@+id/schedule_view" android:layout_width="@dimen/main_card_width" android:layout_height="@dimen/main_card_height" android:layout_gravity="center_horizontal" android:layout_marginTop="@dimen/main_card_margin_top" android:focusable="true" android:nextFocusLeft="@+id/contacts_view" android:nextFocusRight="@+id/call_view"> </com.base.module.gvclauncher2.ui.ScheduleView>
其中android:layout_gravity=“center_horizontal”,使卡片在界面的正中間,其余兩張的卡片也是如此,達到3個View的起始位置一直,這樣方便之后的動畫旋轉。
2.添加自定義ScheduleView
public class ScheduleView extends BasePhoneView { private static final String TAG = "CallFragment"; private static final boolean DEBUG = true; private Context mContext; private View mRootView; private FrameLayout mMainView; private ScheduleContract.View mView; private ScheduleContract.Presenter mPresenter; public ScheduleView(Context context) { super(context); this.mContext = context; initView(); } public ScheduleView(Context context, AttributeSet attrs) { super(context, attrs); this.mContext = context; initView(); } public ScheduleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; initView(); } private void initView() { findView(); initData(); } private void findView() { mRootView = LayoutInflater.from(mContext).inflate(R.layout.fragment_schedule, this); mMainView = (FrameLayout) mRootView.findViewById(R.id.schedule_contains); mMainView.setVisibility(View.VISIBLE); } private void initData() { mMainView.removeAllViews(); mView = ScheduleContractFactory.createScheduleView(mContext); mMainView.addView((View) mView); mPresenter = ScheduleContractFactory.createSchedulePresenter(mContext, mView); mPresenter.onCreate(); //這里只是使用mvp的形式添加view. } @Override public void clearAllFocus() { //清除所有的焦點 if (mView != null) { mView.clearAllFocus(); } } @Override public void requestFirstFocus() { //第一個控件強行指定焦點 if (mView != null) { mView.requestFirstFocus(); } } @Override public void updateListData() { //更新列表顯示 if (mPresenter != null) { mPresenter.reloadConferenceList(); } } }
其中fragment_schedule.xml中只有一個簡單的FrameLayout. View2 和View3類似。
3. 動畫Util
(1) 設置3個卡片的初始位置
public final static float RUN_Y = 22.0f; public final static float RUN_LARGE_Y = 24.0f; public final static float RUN_Y_NEGATIVE = -22.0f; public final static float RUN_LARGE_Y_NEGATIVE = -24.0f; public final static float RUN_X = 1235.0f; public final static float RUN_X_NEGATIVE = -1235.0f; public final static float RUN_LARGE_X = 1366.0f; public final static float RUN_LARGE_X_NEGATIVE = -1366.0f; public void initLeftAnimator(View leftView) { leftView.setTranslationX(RUN_X_NEGATIVE);//離屏幕中心偏移距離 leftView.setRotationY(RUN_Y);//旋轉角度 leftView.setAlpha(LEFT_RIGHT_ALPHA);//設置透明度 } public void initRightAnimator(View rightView) { rightView.setTranslationX(RUN_X);//離屏幕中心偏移距離 rightView.setRotationY(RUN_Y_NEGATIVE);//旋轉角度 rightView.setAlpha(LEFT_RIGHT_ALPHA);//設置透明度 } public void initMidAnimator(View midView) { //由于初始位置在xml中設定是在正中間,這里就不重新設置偏移量 midView.setAlpha(MIDDLE_ALPHA); } public void midToLeftAnimator(final View runView, boolean anim) { ObjectAnimator animatorX = ObjectAnimator.ofFloat(runView, "translationX", 0, RUN_X_NEGATIVE); //中間的起始位置未0 ObjectAnimator animatorZ = ObjectAnimator.ofFloat(runView, "rotationY", 0, RUN_Y); ObjectAnimator animator3 = ObjectAnimator.ofFloat(runView, "alpha", MIDDLE_ALPHA, LEFT_RIGHT_ALPHA); mMidToLeftAnimator = new AnimatorSet(); mMidToLeftAnimator.play(animatorX).with(animatorZ).with(animator3); //anim設置是否需要動畫執行時間 if (anim) { mMidToLeftAnimator.setDuration(DURATION); } else { mMidToLeftAnimator.setDuration(0); } mMidToLeftAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { mIsScrolling = true; } @Override public void onAnimationEnd(Animator animation) { //mIsScrolling來判斷動畫是否完成,來控制下一次動畫是否需要執行 mIsScrolling = false; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mMidToLeftAnimator.start(); } public void midToRightAnimator(final View runView, boolean anim) { ObjectAnimator animatorX = ObjectAnimator.ofFloat(runView, "translationX", 0, RUN_X); ObjectAnimator animatorZ = ObjectAnimator.ofFloat(runView, "rotationY", 0, RUN_Y_NEGATIVE); ObjectAnimator animator3 = ObjectAnimator.ofFloat(runView, "alpha", MIDDLE_ALPHA, LEFT_RIGHT_ALPHA); mMidToRightAnimator = new AnimatorSet(); mMidToRightAnimator.play(animatorX).with(animatorZ).with(animator3); if (anim) { mMidToRightAnimator.setDuration(DURATION); } else { mMidToRightAnimator.setDuration(0); } mMidToRightAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { mIsScrolling = true; } @Override public void onAnimationEnd(Animator animation) { mIsScrolling = false; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mMidToRightAnimator.start(); } public void rightToMidAnimator(final View runView, boolean anim) { ObjectAnimator animatorX = ObjectAnimator.ofFloat(runView, "translationX", RUN_X, 0); ObjectAnimator animatorZ = ObjectAnimator.ofFloat(runView, "rotationY", RUN_Y_NEGATIVE, 0); ObjectAnimator animator3 = ObjectAnimator.ofFloat(runView, "alpha", LEFT_RIGHT_ALPHA, MIDDLE_ALPHA); mRightToMidAnimator = new AnimatorSet(); mRightToMidAnimator.play(animatorX).with(animatorZ).with(animator3); if (anim) { mRightToMidAnimator.setDuration(DURATION); } else { mRightToMidAnimator.setDuration(0); } mRightToMidAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { mIsScrolling = true; } @Override public void onAnimationEnd(Animator animation) { mIsScrolling = false; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mRightToMidAnimator.start(); } public void leftToMidAnimator(final View runView, boolean anim) { ObjectAnimator animatorX = ObjectAnimator.ofFloat(runView, "translationX", RUN_X_NEGATIVE, 0); ObjectAnimator animatorZ = ObjectAnimator.ofFloat(runView, "rotationY", RUN_Y, 0); ObjectAnimator animator3 = ObjectAnimator.ofFloat(runView, "alpha", LEFT_RIGHT_ALPHA, MIDDLE_ALPHA); mLeftToMidAnimator = new AnimatorSet(); mLeftToMidAnimator.play(animatorX).with(animatorZ).with(animator3); if (anim) { mLeftToMidAnimator.setDuration(DURATION); } else { mLeftToMidAnimator.setDuration(0); } mLeftToMidAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { mIsScrolling = true; } @Override public void onAnimationEnd(Animator animation) { mIsScrolling = false; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mLeftToMidAnimator.start(); } public void rightToLeftAnimator(View runView, boolean anim) { ObjectAnimator animator1 = ObjectAnimator.ofFloat(runView, "translationX", RUN_X, RUN_LARGE_X); ObjectAnimator animator2 = ObjectAnimator.ofFloat(runView, "rotationY", RUN_Y_NEGATIVE, RUN_LARGE_Y_NEGATIVE); ObjectAnimator animator3 = ObjectAnimator.ofFloat(runView, "alpha", LEFT_RIGHT_ALPHA, 0.0f); //繼續往右偏移 ObjectAnimator animator4 = ObjectAnimator.ofFloat(runView, "translationX", RUN_LARGE_X, RUN_X_NEGATIVE); ObjectAnimator animator5 = ObjectAnimator.ofFloat(runView, "rotationY", RUN_LARGE_Y_NEGATIVE, RUN_LARGE_Y); //中途隱藏不顯示 ObjectAnimator animator6 = ObjectAnimator.ofFloat(runView, "alpha", 0.0f, 0.0f); ObjectAnimator animator7 = ObjectAnimator.ofFloat(runView, "translationX", RUN_X_NEGATIVE, RUN_X_NEGATIVE); //往左偏移顯示在左邊位置 ObjectAnimator animator8 = ObjectAnimator.ofFloat(runView, "rotationY", RUN_LARGE_Y, RUN_Y); ObjectAnimator animator9 = ObjectAnimator.ofFloat(runView, "alpha", LEFT_RIGHT_ALPHA, LEFT_RIGHT_ALPHA); //給分段動畫設置時間 if (anim) { animator1.setDuration(170); animator4.setDuration(60); animator7.setDuration(170); } else { animator1.setDuration(0); animator4.setDuration(0); animator7.setDuration(0); } //with:同時執行,after(動畫1):在動畫1之后執行,befor(動畫1):在動畫1之前執行。 //請注意以下的after(animator1)。表示動畫4.5.6在動畫1,2,3執行完畢之后同時執行 mRightToLeftAnimator = new AnimatorSet(); mRightToLeftAnimator.play(animator1).with(animator2).with(animator3); mRightToLeftAnimator.play(animator4).with(animator5).with(animator6).after(animator1); mRightToLeftAnimator.play(animator7).with(animator8).with(animator9).after(animator4); mRightToLeftAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { mIsScrolling = true; } @Override public void onAnimationEnd(Animator animation) { //mIsScrolling = false; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mRightToLeftAnimator.start(); } public void leftToRightAnimator(View runView, boolean anim) { ObjectAnimator animator1 = ObjectAnimator.ofFloat(runView, "translationX", RUN_X_NEGATIVE, RUN_LARGE_X_NEGATIVE); ObjectAnimator animator2 = ObjectAnimator.ofFloat(runView, "rotationY", RUN_Y, RUN_LARGE_Y); ObjectAnimator animator3 = ObjectAnimator.ofFloat(runView, "alpha", LEFT_RIGHT_ALPHA, 0.0f); ObjectAnimator animator4 = ObjectAnimator.ofFloat(runView, "translationX", RUN_LARGE_X_NEGATIVE, RUN_X); ObjectAnimator animator5 = ObjectAnimator.ofFloat(runView, "rotationY", RUN_LARGE_Y, RUN_LARGE_Y_NEGATIVE); ObjectAnimator animator6 = ObjectAnimator.ofFloat(runView, "alpha", 0.0f, 0.0f); ObjectAnimator animator7 = ObjectAnimator.ofFloat(runView, "translationX", RUN_X, RUN_X); ObjectAnimator animator8 = ObjectAnimator.ofFloat(runView, "rotationY", RUN_LARGE_Y_NEGATIVE, RUN_Y_NEGATIVE); ObjectAnimator animator9 = ObjectAnimator.ofFloat(runView, "alpha", LEFT_RIGHT_ALPHA, LEFT_RIGHT_ALPHA); if (anim) { animator1.setDuration(170); animator4.setDuration(60); animator7.setDuration(170); } else { animator1.setDuration(0); animator4.setDuration(0); animator7.setDuration(0); } mLeftToRightAnimator = new AnimatorSet(); mLeftToRightAnimator.play(animator1).with(animator2).with(animator3); mLeftToRightAnimator.play(animator4).with(animator5).with(animator6).after(animator1); mLeftToRightAnimator.play(animator7).with(animator8).with(animator9).after(animator4); mLeftToRightAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { mIsScrolling = true; } @Override public void onAnimationEnd(Animator animation) { //mIsScrolling = false; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mLeftToRightAnimator.start(); }
好了,基本的動畫效果就是這些了,其他細節就不貼代碼了。
4. ScheduleView添加的子View焦點處理
@Override public void clearAllFocus() { mConfListView.clearFocus(); mLoginBtn.clearFocus(); updateAllFocus(false); updateAllClickable(false); } @Override public void requestFirstFocus() { updateAllFocus(true); updateAllClickable(true); if (mPresenter.hasLogin()) { mConfListView.requestFocus(); } else { mLoginBtn.requestFocus(); } } private void updateAllFocus(boolean focus) { mConfListView.setFocusable(focus); mLoginBtn.setFocusable(focus); } private void updateAllClickable(boolean enabled) { mConfListView.setEnabled(enabled); mLoginBtn.setFocusable(enabled); }
當ScheduleView偏離中間位置時,需要清楚當前界面所有的焦點并使其不能點擊。
當ScheduleView旋轉到中間的時候需要重新使其獲取到焦點讓其能夠點擊。
左右旋轉控制
@Override public boolean dispatchKeyEvent(KeyEvent event) { int keyCode = event.getKeyCode(); int action = event.getAction(); Log.d(TAG, "keyCode v = " + keyCode); switch (keyCode) { case KeyEvent.KEYCODE_DPAD_LEFT: if (action == KeyEvent.ACTION_UP) { if (mCurrentItem == ITEMTAG.CALL && mCallView.isLastFocus(0)) { runLeftControl(true); } else if (mCurrentItem == ITEMTAG.CONTACTS && mContactsView.isLastFocus(0)) { runLeftControl(true); } else if (mCurrentItem == ITEMTAG.SCHEDULE) { runLeftControl(true); } } else if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { if (mCurrentItem == ITEMTAG.CALL) { mCallView.saveLastFocusId(); } } break; case KeyEvent.KEYCODE_DPAD_RIGHT: if (action == KeyEvent.ACTION_UP) { if (mCurrentItem == ITEMTAG.CALL && mCallView.isLastFocus(1)) { runRightControl(true); } else if (mCurrentItem == ITEMTAG.CONTACTS && mContactsView.isLastFocus(1)) { runRightControl(true); } else if (mCurrentItem == ITEMTAG.SCHEDULE) { runRightControl(true); } } else if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { if (mCurrentItem == ITEMTAG.CALL) { mCallView.saveLastFocusId(); } } break; case KeyEvent.KEYCODE_MENU: if (action == KeyEvent.ACTION_UP) { AnimatorManager.getInstance().shakeView(mTitleMenuView, 0.5f); } break; } return super.dispatchKeyEvent(event); }
(1)處理方式是在監聽遙控器的左右按鍵的UP事件,使其避免在Down時候處理旋轉太快系統ANR
(2)如果界面中含有EditView,如果其在界面的邊緣,那么左右按鍵就會和EditText有字符的時候就會對是否是最邊沿的判斷isLastFocus(0)產生影響。解決方案:
<Button android:id="@+id/go_left_btn" android:layout_width="1dp" android:layout_height="1dp" android:background="@null" android:clickable="false" android:focusable="true" /> <Button android:id="@+id/go_right_btn" android:layout_width="12dp" android:layout_height="match_parent" android:background="@null" android:clickable="false" android:focusable="true" android:nextFocusLeft="@+id/et_search" android:nextFocusRight="@+id/go_right_btn" />
在最左邊go_left_btn和最右邊go_right_btn添加隱形的btn,讓其獲取焦點,在左右按鍵UP的時候,焦點如果在兩個按鈕上就實行左右跳轉,否則停留在當前界面。
感謝各位的閱讀,以上就是“怎么實現Android TV 3D卡片無限循環效果”的內容了,經過本文的學習后,相信大家對怎么實現Android TV 3D卡片無限循環效果這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。