溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么實現(xiàn)Android TV 3D卡片無限循環(huán)效果

發(fā)布時間:2021-11-04 13:42:10 來源:億速云 閱讀:122 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要講解了“怎么實現(xiàn)Android TV 3D卡片無限循環(huán)效果”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“怎么實現(xiàn)Android TV 3D卡片無限循環(huán)效果”吧!

##思路

自定義View + 屬性動畫ObjectAnimator
按鍵事件特殊處理。

##實現(xiàn)方式

1、ObjectAnimator屬性動畫的知識準(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個View的起始位置一直,這樣方便之后的動畫旋轉(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() {
    //清除所有的焦點
        if (mView != null) {
            mView.clearAllFocus();
        }
    }

    @Override
    public void requestFirstFocus() {
    //第一個控件強(qiáng)行指定焦點
        if (mView != null) {
            mView.requestFirstFocus();
        }
    }

    @Override
    public void updateListData() {
    //更新列表顯示
        if (mPresenter != null) {
            mPresenter.reloadConferenceList();
        }
    }

}

其中fragment_schedule.xml中只有一個簡單的FrameLayout. View2 和View3類似。

3. 動畫Util

(1) 設(shè)置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);//旋轉(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è)置是否需要動畫執(zhí)行時間
        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來判斷動畫是否完成,來控制下一次動畫是否需要執(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);

        //給分段動畫設(shè)置時間
        if (anim) {
            animator1.setDuration(170);
            animator4.setDuration(60);
            animator7.setDuration(170);
        } else {
            animator1.setDuration(0);
            animator4.setDuration(0);
            animator7.setDuration(0);
        }
//with:同時執(zhí)行,after(動畫1):在動畫1之后執(zhí)行,befor(動畫1):在動畫1之前執(zhí)行。
//請注意以下的after(animator1)。表示動畫4.5.6在動畫1,2,3執(zhí)行完畢之后同時執(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();
}

好了,基本的動畫效果就是這些了,其他細(xì)節(jié)就不貼代碼了。

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);
    }

當(dāng)ScheduleView偏離中間位置時,需要清楚當(dāng)前界面所有的焦點并使其不能點擊。
當(dāng)ScheduleView旋轉(zhuǎ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時候處理旋轉(zhuǎn)太快系統(tǒng)ANR
(2)如果界面中含有EditView,如果其在界面的邊緣,那么左右按鍵就會和EditText有字符的時候就會對是否是最邊沿的判斷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,讓其獲取焦點,在左右按鍵UP的時候,焦點如果在兩個按鈕上就實行左右跳轉(zhuǎn),否則停留在當(dāng)前界面。

感謝各位的閱讀,以上就是“怎么實現(xiàn)Android TV 3D卡片無限循環(huán)效果”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對怎么實現(xiàn)Android TV 3D卡片無限循環(huán)效果這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI