千家信息网

怎么实现Android TV 3D卡片无限循环效果

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,这篇文章主要讲解了"怎么实现Android TV 3D卡片无限循环效果",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"怎么实现Android TV 3D
千家信息网最后更新 2026年11月02日怎么实现Android TV 3D卡片无限循环效果

这篇文章主要讲解了"怎么实现Android TV 3D卡片无限循环效果",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"怎么实现Android TV 3D卡片无限循环效果"吧!

##思路

自定义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.    }    @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)产生影响。解决方案:

在最左边go_left_btn和最右边go_right_btn添加隐形的btn,让其获取焦点,在左右按键UP的时候,焦点如果在两个按钮上就实行左右跳转,否则停留在当前界面。

感谢各位的阅读,以上就是"怎么实现Android TV 3D卡片无限循环效果"的内容了,经过本文的学习后,相信大家对怎么实现Android TV 3D卡片无限循环效果这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

动画 卡片 焦点 位置 效果 界面 偏移 循环 按键 时候 处理 学习 事件 内容 同时 就是 屏幕 属性 思路 方式 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技公司起名 互联网科技的发展 互联网科技小贷 智网互联网科技 万易互联网科技有限公司 汇久创客互联网科技 齐家互联网科技有限公司 互联网科技类展会 青海省互联网新科技 互联网科技公司招聘 剑昆互联网络科技有限公司临桂 欧洲十大科技互联网公司 浙江互联网科技研学营 允成互联网科技有限公司融资 佛山市翊博互联网络科技有限公司 银行科技岗属于互联网吗 领航科技大厦互联网公司 半导体是互联网高科技吗 上海乐五互联网科技有限公司 北京瑞云互联网科技有限公司 互联网科技与金融科技 互联网金融公司科技 奇酷互联网科技公司 阿里互联网科技公司 互联网和黑科技的看法 河南猎农互联网科技有限公司 语蝶互联网科技有限公司 佛山市拉法互联网科技有限公司 国大互联网教育科技有限公司薪资 卡尔迅互联网科技有限公司
0