Android TV 3D卡片無(wú)限循環(huán)效果
TV 3D卡片無(wú)限循環(huán)效果,供大家參考,具體內(nèi)容如下
##前言
1、需求:實(shí)現(xiàn)3個(gè)卡片實(shí)現(xiàn)無(wú)限循環(huán)效果:1-2-3-1-2-3-1…,而且要實(shí)現(xiàn)3D效果:中間突出,兩側(cè)呈角度顯示
2、Viewpager實(shí)現(xiàn)方式
(1) LoopViewpager,有興趣的同學(xué)可以去github上看一下。
(2) 通過定義一個(gè)item的個(gè)數(shù)Integer,MAX,然后設(shè)置初始位置為:Integer,MAX/2。
以上方式如果簡(jiǎn)單的加載圖片這種方式還可取,由于需求3個(gè)界面內(nèi)部控件比較多,在加上需要實(shí)現(xiàn)自定義的的3D效果,使用ViewPager實(shí)現(xiàn)難為了小編,于是舍棄只能自己碼代碼了,欲哭無(wú)淚?。?!
##思路
自定義View + 屬性動(dòng)畫ObjectAnimator
按鍵事件特殊處理。
##實(shí)現(xiàn)方式
1、ObjectAnimator屬性動(dòng)畫的知識(shí)準(zhǔn)備。
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”,使卡片在界面的正中間,其余兩張的卡片也是如此,達(dá)到3個(gè)View的起始位置一直,這樣方便之后的動(dòng)畫旋轉(zhuǎn)。
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() {
//清除所有的焦點(diǎn)
if (mView != null) {
mView.clearAllFocus();
}
}
@Override
public void requestFirstFocus() {
//第一個(gè)控件強(qiáng)行指定焦點(diǎn)
if (mView != null) {
mView.requestFirstFocus();
}
}
@Override
public void updateListData() {
//更新列表顯示
if (mPresenter != null) {
mPresenter.reloadConferenceList();
}
}
}
其中fragment_schedule.xml中只有一個(gè)簡(jiǎn)單的FrameLayout. View2 和View3類似。
3. 動(dòng)畫Util
(1) 設(shè)置3個(gè)卡片的初始位置
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);//旋轉(zhuǎn)角度
leftView.setAlpha(LEFT_RIGHT_ALPHA);//設(shè)置透明度
}
public void initRightAnimator(View rightView) {
rightView.setTranslationX(RUN_X);//離屏幕中心偏移距離
rightView.setRotationY(RUN_Y_NEGATIVE);//旋轉(zhuǎn)角度
rightView.setAlpha(LEFT_RIGHT_ALPHA);//設(shè)置透明度
}
public void initMidAnimator(View midView) {
//由于初始位置在xml中設(shè)定是在正中間,這里就不重新設(shè)置偏移量
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設(shè)置是否需要?jiǎng)赢媹?zhí)行時(shí)間
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來判斷動(dòng)畫是否完成,來控制下一次動(dòng)畫是否需要執(zhí)行
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);
//繼續(xù)往右偏移
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);
//給分段動(dòng)畫設(shè)置時(shí)間
if (anim) {
animator1.setDuration(170);
animator4.setDuration(60);
animator7.setDuration(170);
} else {
animator1.setDuration(0);
animator4.setDuration(0);
animator7.setDuration(0);
}
//with:同時(shí)執(zhí)行,after(動(dòng)畫1):在動(dòng)畫1之后執(zhí)行,befor(動(dòng)畫1):在動(dòng)畫1之前執(zhí)行。
//請(qǐng)注意以下的after(animator1)。表示動(dòng)畫4.5.6在動(dòng)畫1,2,3執(zhí)行完畢之后同時(shí)執(zhí)行
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();
}
好了,基本的動(dòng)畫效果就是這些了,其他細(xì)節(jié)就不貼代碼了。
4. ScheduleView添加的子View焦點(diǎn)處理
@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);
}
當(dāng)ScheduleView偏離中間位置時(shí),需要清楚當(dāng)前界面所有的焦點(diǎn)并使其不能點(diǎn)擊。
當(dāng)ScheduleView旋轉(zhuǎn)到中間的時(shí)候需要重新使其獲取到焦點(diǎn)讓其能夠點(diǎn)擊。
左右旋轉(zhuǎn)控制
@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)處理方式是在監(jiān)聽遙控器的左右按鍵的UP事件,使其避免在Down時(shí)候處理旋轉(zhuǎn)太快系統(tǒng)ANR
(2)如果界面中含有EditView,如果其在界面的邊緣,那么左右按鍵就會(huì)和EditText有字符的時(shí)候就會(huì)對(duì)是否是最邊沿的判斷isLastFocus(0)產(chǎn)生影響。解決方案:
<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,讓其獲取焦點(diǎn),在左右按鍵UP的時(shí)候,焦點(diǎn)如果在兩個(gè)按鈕上就實(shí)行左右跳轉(zhuǎn),否則停留在當(dāng)前界面。
6. 總結(jié)
(1)、實(shí)現(xiàn)方式其實(shí)還是比較簡(jiǎn)單的,view+animator.
(2)、后續(xù)需要實(shí)現(xiàn)鼠標(biāo)拖拽旋轉(zhuǎn)效果,思路:監(jiān)聽按鍵的Down/MOVE/UP事件,做點(diǎn)動(dòng)畫的開始、移動(dòng)、停止。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android 使用viewpager實(shí)現(xiàn)無(wú)限循環(huán)(定時(shí)+手動(dòng))
- Android viewpager中動(dòng)態(tài)添加view并實(shí)現(xiàn)偽無(wú)限循環(huán)的方法
- Android ViewPager無(wú)限循環(huán)實(shí)現(xiàn)底部小圓點(diǎn)動(dòng)態(tài)滑動(dòng)
- Android ViewPager無(wú)限循環(huán)滑動(dòng)并可自動(dòng)滾動(dòng)完整實(shí)例
- Android無(wú)限循環(huán)RecyclerView的完美實(shí)現(xiàn)方案
- Android ViewPager實(shí)現(xiàn)無(wú)限循環(huán)效果
- Android實(shí)現(xiàn)ViewPager無(wú)限循環(huán)效果(一)
- Android實(shí)現(xiàn)帶指示點(diǎn)的自動(dòng)輪播無(wú)限循環(huán)效果
- Android實(shí)現(xiàn)基于ViewPager的無(wú)限循環(huán)自動(dòng)播放帶指示器的輪播圖CarouselFigureView控件
- Android實(shí)戰(zhàn)打飛機(jī)游戲之無(wú)限循環(huán)的背景圖(2)
相關(guān)文章
android surfaceView實(shí)現(xiàn)播放視頻功能
這篇文章主要為大家詳細(xì)介紹了android surfaceView實(shí)現(xiàn)播放視頻功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05
android 獲取手機(jī)內(nèi)存及 內(nèi)存可用空間的方法
下面小編就為大家?guī)硪黄猘ndroid 獲取手機(jī)內(nèi)存及SD卡內(nèi)存可用空間的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-03-03
android使用TextView實(shí)現(xiàn)跑馬燈效果
這篇文章主要為大家詳細(xì)介紹了android使用TextView實(shí)現(xiàn)跑馬燈效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05
Android studio報(bào): java.lang.ExceptionInInitializerError 錯(cuò)誤
本篇文章主要介紹了Android studio報(bào): java.lang.ExceptionInInitializerError錯(cuò)誤的解決方法,具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-03-03
Android實(shí)現(xiàn)3D層疊式卡片圖片展示
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)3D層疊式卡片圖片展示,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10
Android定時(shí)器Timer的停止和重啟實(shí)現(xiàn)代碼
本篇文章主要介紹了Android實(shí)現(xiàn)定時(shí)器Timer的停止和重啟實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
一文詳解Android無(wú)需權(quán)限調(diào)用系統(tǒng)相機(jī)拍照
這篇文章主要為大家介紹了Android無(wú)需權(quán)限調(diào)用系統(tǒng)相機(jī)拍照詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
Android應(yīng)用開發(fā)中自定義ViewGroup的究極攻略
這里我們要演示的自定義ViewGroup中將實(shí)現(xiàn)多種方式排列和滑動(dòng)等效果,并且涵蓋子View之間Touch Event的攔截與處理等問題,完全干貨,下面就為大家送上Android應(yīng)用開發(fā)中自定義ViewGroup的究極實(shí)例攻略2016-05-05

