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实现难为了小编,于是舍弃只能自己码代码了,欲哭无泪!!!

##思路

  1. 自定义View + 属性动画ObjectAnimator
  2. 按键事件特殊处理。

##实现方式

  1. ObjectAnimator属性动画的知识准备。
  2. 父不居中自定义ScheduleView,View2, View3
<com.base.module.gvclauncher2.ui.ScheduleViewandroid: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.}@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类似。
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旋转到中间的时候需要重新使其获取到焦点让其能够点击。

  1. 左右旋转控制
@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)产生影响。解决方案:

<Buttonandroid:id="@+id/go_left_btn"android:layout_width="1dp"android:layout_height="1dp"android:background="@null"android:clickable="false"android:focusable="true" />
<Buttonandroid: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的时候,焦点如果在两个按钮上就实行左右跳转,否则停留在当前界面。
6. 总结
(1)实现方式其实还是比较简单的,view+animator.
(2)后续需要实现鼠标拖拽旋转效果,思路:监听按键的Down/MOVE/UP事件,做点动画的开始、移动、停止。

Android TV 3D卡片无限循环效果相关推荐

  1. android双重for循环,Android实现ViewPager无限循环效果(二)

    本文实例为大家分享了Android实现ViewPager无限循环效果的第二种方式,供大家参考,具体内容如下 原理:在Adapter中将getCount设置为无限大 package com.xiaoma ...

  2. android星星爆炸效果图,Android_Android仿开心消消乐大树星星无限循环效果,啥都不说先上效果图,这个是 - phpStudy...

    Android仿开心消消乐大树星星无限循环效果 啥都不说先上效果图,这个是我项目里的效果: 下面的是我抽取出来的 demo 适配啥的我基本上都做好了没做其他的 ok 下面 说一下思路把 首先 说一下原 ...

  3. 【Android】ViewPager实现无限循环滚动

    最近做的一个项目,客户要求在ViewPager实现的主页面中滑动到最后一页后继续滑动能返回到第一页,也就是实现无限循环滚动,效果如下: 看了下ViewPager没有滑到尽头的回调方法,因此想到的解决方 ...

  4. ViewPager实现左右无限循环效果

    ViewPager自身并不支持左右无限循环的功能,在网上找了很多天,发现基本都是一个原理,就是实现一种假的无限循环,取一种最大值的思路,这一种方案在上一篇中实现过,并没有真正达到左右无限循环,只是一般 ...

  5. android无限旋转动画,android animation rotate 图片无限循环旋转

    需要保持界面风格一致,告诉用户正在加载,用animation实现,很简单 android:interpolator="@android :anim/cycle_interpolator&qu ...

  6. android 星星流逝动画,Android仿开心消消乐大树星星无限循环效果

    啥都不说先上效果图,这个是我项目里的效果: 下面的是我抽取出来的 demo 适配啥的我基本上都做好了没做其他的 ok 下面 说一下思路把 首先 说一下原理 我是使用bitmap 创建两张图 一开始的时 ...

  7. android 无限旋转动画,Android 属性动画之无限循环缩放动画,旋转动画

    缩放动画 AnimatorSet animatorSetsuofang = new AnimatorSet();//组合动画 ObjectAnimator scaleX = ObjectAnimato ...

  8. android开发实例之viewpager无限循环+自动滚动,Android ViewPager实现无限循环的实例...

    Android ViewPager实现无限循环的实例 ViewPager自身并不支持左右无限循环的功能,这里就提供一种方案让Android ViewPager实现左右无限循环的功能,这里记录下: 用于 ...

  9. 实现一个横向无限循环滚动的单行弹幕效果

    本期将带领大家实现一个这样的效果,支持无限循环的单行弹幕效果. 实现思路分析 要实现上面的效果,我们先拆分下实现要素: 1.弹幕布局是从屏幕的右侧向左侧滚动,单个弹幕之间的间距是固定的(设计要求) 2 ...

最新文章

  1. php 7.1/7.3使用 json_encode 函数造成浮点类型数据出现精度问题
  2. python心得-基本概念2
  3. etcd 启动分析_Etcd 架构与实现解析
  4. TypeError: 'float' object is not iterable
  5. 无锡太湖学院计算机科学与技术宿舍,无锡太湖学院宿舍怎么样
  6. 快速排序——主要思想是分治
  7. [计算机网络] - 从英雄联盟,看数据包何去何从?
  8. 程序员写三十行代码,被应届生怼:我能三行搞定!也配叫程序员?
  9. 单片机毕业设计 stm32车牌识别系统
  10. java程序的结构与类型实验报告_20172301 《Java软件结构与数据结构》实验三报告...
  11. 聊聊大数据平台上云这点事
  12. 千张照片合成你一张美照-【OpenCV实战二】
  13. 【附源码】计算机毕业设计SSM校园流浪猫关爱系统
  14. 【小经验】Windows 11 家庭中文版连接远程桌面,出现身份验证错误。要求的函数不受支持
  15. 不只是A/B测试:多臂老虎机赌徒实验
  16. 前端HTMLtable标签的属性和使用
  17. 病理与病理生理学【2】
  18. 飞机订票管理系统C语言课程设计
  19. 试试54款开源服务器软件
  20. python自动收取蚂蚁森林能量_通过测试工具自动收取蚂蚁森林能量

热门文章

  1. 小程序自定义导航栏(适配不同手机)——拿来就用
  2. Baiduman的经历
  3. python中元组怎么存放元素_关于python列表和元组的基本操作
  4. 公司服务器内网OA网站如何实现外网访问?
  5. c#+AE-属性查询之框选查询
  6. linux Docker使用微信
  7. js删除对象属性的方法
  8. 面具busybox模块_自定义内核及busybox系统定制
  9. Python 链表反转
  10. u盘启动蓝屏 索尼vaio_使用快启动u盘启动制作工具为索尼笔记本安装