Android|Android TV 3D卡片无限循环效果

TV 3D卡片无限循环效果,供大家参考,具体内容如下
##前言 1、需求:实现3个卡片实现无限循环效果:1-2-3-1-2-3-1…,而且要实现3D效果:中间突出,两侧呈角度显示
2、Viewpager实现方式

(1) LoopViewpager,有兴趣的同学可以去github上看一下。
(2) 通过定义一个item的个数Integer,MAX,然后设置初始位置为:Integer,MAX/2。

以上方式如果简单的加载图片这种方式还可取,由于需求3个界面内部控件比较多,在加上需要实现自定义的的3D效果,使用ViewPager实现难为了小编,于是舍弃只能自己码代码了,欲哭无泪!!!

##思路 自定义View + 属性动画ObjectAnimator
按键事件特殊处理。

##实现方式 1、ObjectAnimator属性动画的知识准备。
2、父不居中自定义ScheduleView,View2, View3


其中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.}@Overridepublic void clearAllFocus() {//清除所有的焦点if (mView != null) {mView.clearAllFocus(); }}@Overridepublic void requestFirstFocus() {//第一个控件强行指定焦点if (mView != null) {mView.requestFirstFocus(); }}@Overridepublic void updateListData() {//更新列表显示if (mPresenter != null) {mPresenter.reloadConferenceList(); }}}

其中fragment_schedule.xml中只有一个简单的FrameLayout. View2 和View3类似。
【Android|Android TV 3D卡片无限循环效果】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); //中间的起始位置未0ObjectAnimator 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() {@Overridepublic void onAnimationStart(Animator animation) {mIsScrolling = true; }@Overridepublic void onAnimationEnd(Animator animation) {//mIsScrolling来判断动画是否完成,来控制下一次动画是否需要执行mIsScrolling = false; }@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animation) {mIsScrolling = true; }@Overridepublic void onAnimationEnd(Animator animation) {mIsScrolling = false; }@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animation) {mIsScrolling = true; }@Overridepublic void onAnimationEnd(Animator animation) {mIsScrolling = false; }@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animation) {mIsScrolling = true; }@Overridepublic void onAnimationEnd(Animator animation) {mIsScrolling = false; }@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animation) {mIsScrolling = true; }@Overridepublic void onAnimationEnd(Animator animation) {//mIsScrolling = false; }@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animation) {mIsScrolling = true; }@Overridepublic void onAnimationEnd(Animator animation) {//mIsScrolling = false; }@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}}); mLeftToRightAnimator.start(); }

好了,基本的动画效果就是这些了,其他细节就不贴代码了。
4. ScheduleView添加的子View焦点处理
@Overridepublic void clearAllFocus() {mConfListView.clearFocus(); mLoginBtn.clearFocus(); updateAllFocus(false); updateAllClickable(false); }@Overridepublic 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旋转到中间的时候需要重新使其获取到焦点让其能够点击。
左右旋转控制
@Overridepublic 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)产生影响。解决方案:

在最左边go_left_btn和最右边go_right_btn添加隐形的btn,让其获取焦点,在左右按键UP的时候,焦点如果在两个按钮上就实行左右跳转,否则停留在当前界面。
6. 总结
(1)、实现方式其实还是比较简单的,view+animator.
(2)、后续需要实现鼠标拖拽旋转效果,思路:监听按键的Down/MOVE/UP事件,做点动画的开始、移动、停止。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

    推荐阅读