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

<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的时候,焦点如果在两个按钮上就实行左右跳转,否则停留在当前界面。

6. 总结

(1)、实现方式其实还是比较简单的,view+animator.
(2)、后续需要实现鼠标拖拽旋转效果,思路:监听按键的Down/MOVE/UP事件,做点动画的开始、移动、停止。

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

(0)

相关推荐

  • Android ViewPager实现无限循环效果

    最近项目里有用到ViewPager来做广告运营位展示,看到现在很多APP的广告运营位都是无限循环的,所以就研究了一下这个功能的实现. 先看看效果 从一个方向上一直滑动,么有滑到尽头的感觉,具体是怎么实现的呢?看下面的思路. 实现思路 此处画了一幅图来表达实现无限循环的思路,即在数据起始位置前插入最后一项数据,在最后一项数据后插入第一项数据,当滑动到此处时,更新页面的索引位置就ok了 . 代码实现 这个方法用于数据处理,其中mediaList是原始数据,newMediaList是处理完的数据,mM

  • Android ViewPager无限循环实现底部小圆点动态滑动

    页面拖动到最后一页 再向下滑动回复到 第一页,第一页向前滑动回到 最后一页 同时,底部红色小圆点随着页面的滑动距离比例随时改变位置 布局: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas

  • Android实现ViewPager无限循环效果(一)

    本文实例为大家分享了Android实现ViewPager无限循环的具体代码,供大家参考,具体内容如下 方式一: 实现原理: 假设有3张图片,分别是1,2,3,那么就创建5张图片,这5张图片的顺序为:3,1,2,3,1,其中1,2,3为我们要实现滑动的图片,最左面的3和最右面的1是我们另外添加的图片,开始时,显示图片1,当图片向左滑动依次为1,2,3,当从第3张图片继续向左滑动,会出现我们多添加的图片1,这时,将当前的index设置为真正的图片1所在的位置. package com.xiaomai

  • Android实现带指示点的自动轮播无限循环效果

    想要实现无限轮播,一直向左滑动,当到最后一个view时,会滑动到第一个,无限- 可以自己写ViewPager然后加handler先实现自动滚动,当然这里我为了项目的进度直接使用了Trinea的Android-auto-scroll-view-pager库,网址:点击进入github 引用库compile('cn.trinea.android.view.autoscrollviewpager:android-auto-scroll-view-pager:1.1.2') { exclude modu

  • Android 使用viewpager实现无限循环(定时+手动)

    循环轮播的方法有两种,一种是使用定时器另外一种是使用手指拨动,相比较而言,定时器实现循环播放比较容易,只要在定时器的消息里加上简单几段代码即可,下面分别就这两种方法给大家详解,具体详情请看下文吧. int count = adapter.getCount(); if (count > 1) { // 多于1个,才循环 int index = viewPager.getCurrentItem(); index = (index + 1) % count; viewPager.setCurrentI

  • Android ViewPager无限循环滑动并可自动滚动完整实例

    对于ViewPager 广告页这个功能很多APP都有这个功能在网上也看过一些资料,我就在这把我自己完整的实现方法写出来吧 基础的ViewPager: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="ma

  • Android实战打飞机游戏之无限循环的背景图(2)

    首先分析下游戏界面内的元素: 无限滚动的背景图, 可以操作的主角,主角的子弹, 主角的血量,两种怪物(敌机),一个boss, boss的爆炸效果. 先看效果图 1.首先实现无限滚动的背景图 原理: 定义两个位图对象 当第一个位图到末尾是 第二个位图从第一个位图的末尾跟上. public class GameBg { // 游戏背景的图片资源 // 为了循环播放,这里定义两个位图对象, // 其资源引用的是同一张图片 private Bitmap bmpBackGround1; private B

  • Android viewpager中动态添加view并实现伪无限循环的方法

    本文实例讲述了Android viewpager中动态添加view并实现伪无限循环的方法.分享给大家供大家参考,具体如下: viewpager的使用,大家都熟悉,它可以实现页面之间左右滑动的切换,这里有一个需求,就是viewpager里面加载的页数不是确定的,而是根据数据的多少来确定的.常见的应用就是在一个新闻的详细页面中,显示与此新闻有关的图片. 下面我们来看一下代码: activity_main.xml <RelativeLayout xmlns:android="http://sch

  • Android实现基于ViewPager的无限循环自动播放带指示器的轮播图CarouselFigureView控件

    最近用到需要无限轮播自动播放的轮播轮播图,网上感觉都有这样那样的问题,于是自己写了一个通用的控件CarouselFigureView. 特点: 1.可以轮播view可以自己定义,不一定是要是ImageView2.指示器默认显示,但是可以隐藏3.可以设置指示器的颜色.间距.大小 4.有基础的可以自己修改代码改变指示器位置,这个应该不难5.可以自己开启和关闭自动轮播,开启轮播的时候可以设置轮播时间间隔,默认3000毫秒 我们先来看看效果图: 然后来看看使用代码 xml代码 <?xml version

  • Android无限循环RecyclerView的完美实现方案

    背景 项目中要实现横向列表的无限循环滚动,自然而然想到了RecyclerView,但我们常用的RecyclerView是不支持无限循环滚动的,所以就需要一些办法让它能够无限循环. 方案选择 方案1 对Adapter进行修改 网上大部分博客的解决方案都是这种方案,对Adapter做修改.具体如下 首先,让 Adapter 的 getItemCount() 方法返回 Integer.MAX_VALUE,使得position数据达到很大很大: 其次,在 onBindViewHolder() 方法里对p

随机推荐