Android 仿當樂游戲詳情頁面(二)


在上一篇文章里面,基本上算是實現(xiàn)了該效果的布局,有了布局,接下來就要對布局進行移動處理。</br>
android 仿當樂游戲詳情頁面(一)

對于移動的分析

通過第一篇文章的分析,在所有控件里面,能移動的只有用于展示游戲簡介和游戲相關數(shù)據(jù)的View,并且該View的移動有以下三種狀態(tài):</br>

  1. 處于頂部的狀態(tài)</br>


    頂部狀態(tài)
  2. 中間狀態(tài)</br>


    中間狀態(tài)
  3. 底部狀態(tài):</br>


    底部狀態(tài)

如上面幾張圖片所示,處于頂部狀態(tài),TabLayout 懸停在Toolbar的下面,而此時,用于介紹游戲簡介的View被移出布局;處于中間狀態(tài)時,Toolbar變?yōu)槿该鳡顟B(tài),當位于底部時,用于展示游戲簡介的View被固定在底部,其它的內容將被移出界面之外。</br>

位置狀態(tài)

為了便于理解,首先像定義幾個字段。

1. mImgShotView ==> 由于展示游戲截圖的View。
2. mContentView ==> 用于展示游戲信息的View。
3. mGInfoView   ==> 用于展示游戲簡介信息的View。
3. mHeadView    ==> 包含mGInfoView和TabLayout的View。
4. mHeadH       ==> mHeadView的高度。
5. mBarH        ==> Toolbar 和 TabLayout的高度。
6. mScreenH     ==> 當前可視屏幕高度。
7. mStateBarH   ==> stateBar高度。
8. mNBarH       ==> NavigationBar高度。
9. mTopL        ==> 位于頂部狀態(tài)時,mContentView 的 Y軸坐標基準位置。
10. mCenterL    ==> 位于中間狀態(tài)時,mContentView 的 Y軸坐標基準位置。
11. mBottomL    ==> 位于底部狀態(tài)時,mContentView 的 Y軸坐標基準位置。
12. mRawY       ==> mContentView相對于當前可視界面的 Y 軸坐標。

頂部狀態(tài)分析

當處于頂部狀態(tài)時,mGInfoView將被移出界面之外;在第一篇文章我們編寫的布局里面,mContentView位于ToolBar下方,因此對于mContentView而言,它的基準坐標(y = 0)在Toolbar正下方;</br>
為了將mGInfoView移除界面之外,mContentView需要將Y坐標移動到-mHeadH + mBarH的位置。</br>
因此mTolL = -mHeadH + mBarH

中間狀態(tài)分析

對于中間狀態(tài),便簡單多了,中間狀態(tài)時,mContentView只需要將Y坐標往下移動到任一位置即可,此時,Toolbar處于完全透明狀態(tài)。</br>
這里,我是將它往下距離它基準位置的150dp 的位置。</br>
因此mCenterL = Util.dp2px(150)

底部狀態(tài)分析

當mContentView處于底部狀態(tài),mGInfoView將被固定在屏幕底部,其它的內容將被除出界面之外。</br>
通過分析,很容易知道:mBottomL = mScreenH - mStateBarH - mNBarH - mHeadH + mBarH

代碼實現(xiàn)

現(xiàn)在,3種狀態(tài)算是分析完成了,接下來便是代碼的編寫。</br>
在android 里面,對控件的移動操作,首先想到的是使用手勢。同時,在手勢移動的過程中,還需要對ToolBar進行透明度處理。</br>
mContentView的手勢移動代碼如下所示:

class SimpleGestureAction extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        if (mRawY <= mTopL && distanceY > 0) {
            mRawY = mTopL;
            return true;
        }
        if (mRawY >= mBottomL && distanceY < 0) {
            mRawY = mBottomL;
            return true;
        }
        mRawY -= distanceY;
        if (mRawY < mCenterL) {
            a += distanceY < 0 ? -0.03 : 0.03;
            if (a < 0.0f) {
                a = 0.0f;
            } else if (a > 1.0f) {
                a = 1.0f;
            }
        } else {
            a = 0.0f;
        }
        if (mRawY <= mTopL) {
            mRawY = mTopL;
            a = 1.0f;
            mBarBg.setAlpha(a);
            mTemp.setAlpha(a);
        }
        mContent.setTranslationY(mRawY);
        if (mRawY >= mCenterL + mBarH) {
            rotationBanner(true);
        }
        return true;
    }
}

以上是手勢移動的全部代碼,都是基本的控件移動操作。

mContentView 回歸操作

在上面的段落中,已經實現(xiàn)了對mContentView的移動操作?,F(xiàn)在,我們可以隨意對布局進行移動了;現(xiàn)在,如果對布局進行移動會發(fā)現(xiàn),在對mContentView移動的過程中,如果放開手指,它并沒有自動回彈到3個基準位置!這樣的操作很不符合用戶體驗,并且也沒有達到三個狀態(tài)的要求。</br>
因此,我們需要定義幾個閥值,當手指離開屏幕的時候,mContentView可以根據(jù)這幾個閥值來判斷它應該回歸到具體哪個基準位置。</br>
閥值的定義如下:

1. 回歸mTolL基準位置    ==> mRawY <= -mStateBarH
2. 回歸mCenterL基準位置 ==> -mStateBarH < mRawY && mRawY <= mCenterL + (mBarH << 1)
3. 回歸mBottomL基準位置 ==> mCenterL + (mBarH << 1) <= mRawY

具體的實現(xiàn)代碼如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_UP:
            if (mRawY <= -mStateBarH) {
                toTop();
            } else if ((-mStateBarH < mRawY && mRawY <= mCenterL + (mBarH << 1))) {
                toCenter();
            } else if (mCenterL + (mBarH << 1) <= mRawY) {
                toBottom();
            }
            return true;
        default:
            if (0 <= a && a <= 1.0f) {
                mBarBg.setAlpha(a);
                mTemp.setAlpha(a);
            }
            mDetector.onTouchEvent(event);
            return super.onTouchEvent(event);
    }
}

/** 回到頂部 */
private void toTop() {
    AnimatorSet    set      = new AnimatorSet();
    ObjectAnimator animator = ObjectAnimator.ofFloat(mContent, "translationY", mRawY, mTopL);
    ObjectAnimator alpha    = ObjectAnimator.ofFloat(mBarBg, "alpha", a, 1.0f);
    ObjectAnimator alpha1   = ObjectAnimator.ofFloat(mTemp, "alpha", a, 1.0f);
    set.setDuration(500);
    set.play(animator).with(alpha).with(alpha1);
    set.start();
    mRawY = mTopL;
    a = 1.0f;
    mBarBg.setAlpha(a);
    mTemp.setAlpha(a);
}
/** 回到中間 */
private void toCenter() {
    ObjectAnimator animator = ObjectAnimator.ofFloat(mContent, "translationY", mRawY, mCenterL);
    animator.setDuration(500);
    animator.start();
    mRawY = mCenterL;
    a = 0.0f;
    mBarBg.setAlpha(a);
    mTemp.setAlpha(a);
    mCurrentState = STATE_CENTER;
    rotationBanner(false);
}
/** 回到底部 */
private void toBottom() {
    ObjectAnimator animator = ObjectAnimator.ofFloat(mContent, "translationY", mRawY, mBottomL);
    animator.setDuration(500);
    animator.start();
    mRawY = mBottomL;
    a = 0.0f;
    mBarBg.setAlpha(a);
    mTemp.setAlpha(a);
    mCurrentState = STATE_BOTTOM;
    rotationBanner(true);
}

現(xiàn)在我們實現(xiàn)了布局的移動,同時也實現(xiàn)了mContentView的回歸操作。這是我們現(xiàn)在的效果:


現(xiàn)在的效果

游戲截圖旋轉實現(xiàn)

現(xiàn)在再看上面的效果,在對mContentView移動時,總感覺缺少點什么,再次回到當樂的游戲詳情效果圖,會看到,在移動的過程中,mImgShotView也會進行相應的操作,當mContentView從中間狀態(tài)移動到底部狀態(tài)時,mImgShotView會執(zhí)行一個動畫旋轉操作。再看我們的效果,由于沒有那個動畫旋轉效果,瞬間感覺low爆了。為了讓效果更佳高大上,讓我們來實現(xiàn)mImgShotView的旋轉動畫吧??!

mImgShotView旋轉實現(xiàn)

在當樂的效果中,mImgShotView的旋轉看起來是ViewPager的旋轉,實則是對ViewPager中Fragment的ImageView進行旋轉,在旋轉的過程中,ImageView在旋轉90°同時會填充整個屏幕。</br>
在這里吐槽一下,看起來這種效果不難實現(xiàn),但是等真正開發(fā)時會出現(xiàn)各種各樣的坑,說多了都是淚,誰做誰知道 :( ?。?!翻遍了這個stackoverflow都沒有好的解決方案,最終研究出來使用屬性動畫是最簡單實現(xiàn)并且性能是最好的??!</br>

為了便于理解,將定義一個字段 mBannerImg ==> 實則是對ViewPager中Fragment中真正用于展示游戲截圖的ImageView.

為了實現(xiàn)這種旋轉放大的效果,在進行屬性動畫編寫時,需要同時執(zhí)行以下三個步驟:

1. 將mBannerImg 進行90°旋轉。
2. 將mBannerImg 移動到屏幕中間。
3. 將mBannerImg 放大并填充整個屏幕。

具體的代碼如下:

/** 旋轉 */
private void rotation(ImageView img, boolean useAnim) {
    int w = Util.getWindowWidth(getActivity()), h = Util.getWindowHeight(getActivity());
    int iw = img.getMeasuredWidth(), ih = img.getMeasuredHeight();
    if (useAnim) {
        ObjectAnimator move = ObjectAnimator.ofFloat(img, "translationY", 0, (h - ih) / 2f);
        move.setDuration(400);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(img, "scaleX", 1.0f, (float) h / iw);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(img, "scaleY", 1.0f, (float) w / ih);
        ObjectAnimator rotation = ObjectAnimator.ofFloat(img, "rotation", 0f, 90f);
        AnimatorSet set = new AnimatorSet();
        set.play(scaleX).with(scaleY).with(rotation).with(move);
        set.setDuration(600);
        set.start();
    } else {
        img.setTranslationY((h - ih) / 2f);
        img.setScaleX((float) h / iw);
        img.setScaleY((float) w / ih);
        img.setRotation(90f);
    }
}

/** 恢復 */
private void resumeRotation(ImageView img, boolean useAnim) {
    int w = Util.getWindowWidth(getActivity()), h = Util.getWindowHeight(getActivity());
    int iw = img.getMeasuredWidth(), ih = img.getMeasuredHeight();
    if (useAnim) {
        ObjectAnimator move = ObjectAnimator.ofFloat(img, "translationY", (h - ih) / 2f, 0);
        move.setDuration(400);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(img, "scaleX", (float) h / iw, 1.0f);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(img, "scaleY", (float) w / ih, 1.0f);
        ObjectAnimator rotation = ObjectAnimator.ofFloat(img, "rotation", 90f, 0f);
        AnimatorSet set = new AnimatorSet();
        set.play(scaleX).with(scaleY).with(rotation).with(move);
        set.setDuration(600);
        set.start();
    } else {
        img.setTranslationY(0f);
        img.setScaleX(1.0f);
        img.setScaleY(1.0f);
        img.setRotation(0f);
    }
}

以上便是旋轉的核心代碼。需要注意的是,mBannerImg在進行旋轉并填充到整個界面的過程中,需要改變自己的高度參數(shù),而在運行中改變View如果需要改變自己的參數(shù),需要在View.post(new runable(){....})的線程里面執(zhí)行;也就意味著,如果要讓上面兩個旋轉方法生效,就需要將它們放在post線程里面,因此需要使用到Handler來執(zhí)行UI的更新操作;我在這里是采用HandlerThread來實現(xiàn)這異步更新UI的操作。完整的旋轉代碼實現(xiàn)可以參考我的Demo例子。

mImgShotView旋轉操作

在上面的文章中,我們已經實現(xiàn)了mBannerImg的旋轉,這個時候運行代碼,移動mContentView時,將出現(xiàn)一個很有趣的現(xiàn)在mBannerImg旋轉了,但只顯示了一截,另一節(jié)被“吃掉了”。</br>
出現(xiàn)這個問題的原因是:在對圖片進行旋轉的過程中,屬性動畫已經改變了mBannerImg的高度參數(shù)。比如在mContentView處于底部狀態(tài)時,mBannerImg的高度已經變?yōu)槠聊坏母叨?,但是作為Fragment容器的mImgShotView的高度還是沒有被改變;這就導致剛才所說的那個問題。</br>
知道了原因,解決就簡單了,對mBannerImg進行操作前,只需要將mImgShotView的參數(shù)修改為與mBannerImg的參數(shù)一致便可,代碼如下所示:

/**
 * 初始化游戲截圖ViewPager
 */
private void setupGameShotVp(final ViewPager viewPager) {
    SimpleViewPagerAdapter adapter = new SimpleViewPagerAdapter(getSupportFragmentManager());
    List<BannerEntity>     data    = getBannerData();
    for (BannerEntity entity : data) {
        adapter.addFrag(ScreenshotFragment.newInstance(entity), "");
    }
    viewPager.setAdapter(adapter);
    viewPager.setOffscreenPageLimit(data.size());
    mIndicator.setViewPager(viewPager);
    viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            mShotVpPosition = position;
        }

        @Override
        public void onPageSelected(int position) {}

        @Override
        public void onPageScrollStateChanged(int state) {}
    });
    //設置Banner圖片高度
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            viewPager.post(new Runnable() {
                @Override
                public void run() {
                    SimpleViewPagerAdapter adapter = (SimpleViewPagerAdapter) mImgVP.getAdapter();
                    int                    h       = (int) getResources().getDimension(R.dimen.game_detail_head_img_vp_height);
                    for (int i = 0, count = adapter.getCount(); i < count; i++) {
                        ScreenshotFragment fragment = (ScreenshotFragment) adapter.getItem(i);
                        if (fragment != null) {
                            fragment.setBannerHeight(h);
                        }
                    }
                }
            });
        }
    });
}

最終的效果

最終效果

</br>
現(xiàn)在布局的移動和截圖旋轉算是完成了,接下來便是需要解決最困難的事件分發(fā)??!

Android 仿當樂游戲詳情頁(三)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容