Android 仿当乐游戏详情页面(二)

作者: dorn19978 | 来源:发表于2016-05-04 14:38 被阅读522次

    在上一篇文章里面,基本上算是实现了该效果的布局,有了布局,接下来就要对布局进行移动处理。</br>
    android 仿当乐游戏详情页面(一)

    对于移动的分析

    通过第一篇文章的分析,在所有控件里面,能移动的只有用于展示游戏简介和游戏相关数据的View,并且该View的移动有以下三种状态:</br>

    1. 处于顶部的状态</br>


      顶部状态
    2. 中间状态</br>


      中间状态
    3. 底部状态:</br>


      底部状态

    如上面几张图片所示,处于顶部状态,TabLayout 悬停在Toolbar的下面,而此时,用于介绍游戏简介的View被移出布局;处于中间状态时,Toolbar变为全透明状态,当位于底部时,用于展示游戏简介的View被固定在底部,其它的内容将被移出界面之外。</br>

    位置状态

    为了便于理解,首先像定义几个字段。

    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        ==> 位于顶部状态时,mContentView 的 Y轴坐标基准位置。
    10. mCenterL    ==> 位于中间状态时,mContentView 的 Y轴坐标基准位置。
    11. mBottomL    ==> 位于底部状态时,mContentView 的 Y轴坐标基准位置。
    12. mRawY       ==> mContentView相对于当前可视界面的 Y 轴坐标。
    

    顶部状态分析

    当处于顶部状态时,mGInfoView将被移出界面之外;在第一篇文章我们编写的布局里面,mContentView位于ToolBar下方,因此对于mContentView而言,它的基准坐标(y = 0)在Toolbar正下方;</br>
    为了将mGInfoView移除界面之外,mContentView需要将Y坐标移动到-mHeadH + mBarH的位置。</br>
    因此mTolL = -mHeadH + mBarH

    中间状态分析

    对于中间状态,便简单多了,中间状态时,mContentView只需要将Y坐标往下移动到任一位置即可,此时,Toolbar处于完全透明状态。</br>
    这里,我是将它往下距离它基准位置的150dp 的位置。</br>
    因此mCenterL = Util.dp2px(150)

    底部状态分析

    当mContentView处于底部状态,mGInfoView将被固定在屏幕底部,其它的内容将被除出界面之外。</br>
    通过分析,很容易知道:mBottomL = mScreenH - mStateBarH - mNBarH - mHeadH + mBarH

    代码实现

    现在,3种状态算是分析完成了,接下来便是代码的编写。</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 回归操作

    在上面的段落中,已经实现了对mContentView的移动操作。现在,我们可以随意对布局进行移动了;现在,如果对布局进行移动会发现,在对mContentView移动的过程中,如果放开手指,它并没有自动回弹到3个基准位置!这样的操作很不符合用户体验,并且也没有达到三个状态的要求。</br>
    因此,我们需要定义几个阀值,当手指离开屏幕的时候,mContentView可以根据这几个阀值来判断它应该回归到具体哪个基准位置。</br>
    阀值的定义如下:

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

    具体的实现代码如下:

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

    现在我们实现了布局的移动,同时也实现了mContentView的回归操作。这是我们现在的效果:


    现在的效果

    游戏截图旋转实现

    现在再看上面的效果,在对mContentView移动时,总感觉缺少点什么,再次回到当乐的游戏详情效果图,会看到,在移动的过程中,mImgShotView也会进行相应的操作,当mContentView从中间状态移动到底部状态时,mImgShotView会执行一个动画旋转操作。再看我们的效果,由于没有那个动画旋转效果,瞬间感觉low爆了。为了让效果更佳高大上,让我们来实现mImgShotView的旋转动画吧!!

    mImgShotView旋转实现

    在当乐的效果中,mImgShotView的旋转看起来是ViewPager的旋转,实则是对ViewPager中Fragment的ImageView进行旋转,在旋转的过程中,ImageView在旋转90°同时会填充整个屏幕。</br>
    在这里吐槽一下,看起来这种效果不难实现,但是等真正开发时会出现各种各样的坑,说多了都是泪,谁做谁知道 :( !!!翻遍了这个stackoverflow都没有好的解决方案,最终研究出来使用属性动画是最简单实现并且性能是最好的!!</br>

    为了便于理解,将定义一个字段 mBannerImg ==> 实则是对ViewPager中Fragment中真正用于展示游戏截图的ImageView.

    为了实现这种旋转放大的效果,在进行属性动画编写时,需要同时执行以下三个步骤:

    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在进行旋转并填充到整个界面的过程中,需要改变自己的高度参数,而在运行中改变View如果需要改变自己的参数,需要在View.post(new runable(){....})的线程里面执行;也就意味着,如果要让上面两个旋转方法生效,就需要将它们放在post线程里面,因此需要使用到Handler来执行UI的更新操作;我在这里是采用HandlerThread来实现这异步更新UI的操作。完整的旋转代码实现可以参考我的Demo例子。

    mImgShotView旋转操作

    在上面的文章中,我们已经实现了mBannerImg的旋转,这个时候运行代码,移动mContentView时,将出现一个很有趣的现在mBannerImg旋转了,但只显示了一截,另一节被“吃掉了”。</br>
    出现这个问题的原因是:在对图片进行旋转的过程中,属性动画已经改变了mBannerImg的高度参数。比如在mContentView处于底部状态时,mBannerImg的高度已经变为屏幕的高度,但是作为Fragment容器的mImgShotView的高度还是没有被改变;这就导致刚才所说的那个问题。</br>
    知道了原因,解决就简单了,对mBannerImg进行操作前,只需要将mImgShotView的参数修改为与mBannerImg的参数一致便可,代码如下所示:

    /**
     * 初始化游戏截图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>
    现在布局的移动和截图旋转算是完成了,接下来便是需要解决最困难的事件分发!!

    Android 仿当乐游戏详情页(三)

    相关文章

      网友评论

        本文标题:Android 仿当乐游戏详情页面(二)

        本文链接:https://www.haomeiwen.com/subject/wobrrttx.html