欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

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

来源: 开发者 投稿于  被查看 7205 次 评论:111

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


写在前面

通过上一篇文章的分析,基本已经了解当乐游戏详情页面的思想思路了,本篇文章主要是实现页面的基本效果。

页面布局

通过上一篇文章分析,已经知道,当乐游戏详情页是通过3个不同层次的布局进行叠加来实现的,为了实现这种层次结构,需要用到RelativeLayout 。
这3个View层次如图所示,分别为:介绍游戏简介的头布局、介绍游戏详情的详情界面、还有toolbar。

介绍游戏简介的头布局:

游戏头布局
如图所示,红色圈圈里面的便是介绍这个游戏的头布局。

layout_game_detail_head.xml




    

    

    

        

        
            
            
            
            
            
            

        

        

        

    

    

    



展示游戏各种详情的内容布局

游戏详情布局
如图所示,黄色圈圈里面的是展示游戏相亲的内容布局。

layout_content.xml




    

    

    

    

ToolBar的布局

layout_bar.xml




    

    

    

主页面的布局

activity_main.xml




    

    

    

    

这些都是一些常规的视图布局,通过在RelativeLayout里面对各个布局进行不同层次的摆放以达到实现复杂界面的效果。

内容界面移动的实现

观察当乐的游戏内容介绍,发现内容界面的移动有如下三种状态:
1、处于顶部的状态
顶部状态
2、中间状态
中间状态
3、底部状态
底部状态

处于顶部状态时,图一中,红色圈圈部分的游戏简介被移出布局之外,并且tab被固定在toobar下面。
如图二所示,当处于中间状态时,toolba完全透明,并且介意游戏各种详情的界面移动到中间,而当其处于底部状态时,由于展示游戏各种信息的布局被移出来界面之外,此时,游戏简介布局被固定在屏幕底部。

在移动的过程中,我们需要几个参数来定义移动布局几个状态所处的位置:

 mTopL = -mHeadH + mBarH;
 mCenterL = Util.dp2px(150);
 mBottomL = mScreenH - mStateBarH - mNBarH - mHeadH + mBarH;
mHeadH 展示游戏信息的头部的View(下图红色圈中的View的高度)
中间状态vcC4tcS437bIIG1CYXJIIHRvb2xiYXK78nRhYmJhcrXEuN+2yA0KPHA+0sa2r7K8vta1xMq1z9a0+sLryOfPwjo8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> /**通过手势控制GameContentView的移动*/ 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; } } @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; // mCurrentState = STATE_TOP; a = 1.0f; mBarBg.setAlpha(a); mTemp.setAlpha(a); // showBottomBar(true); } /** * 回到中间 */ 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); // showBottomBar(true); } /** * 到底部 */ 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);

上面的代码是通过手势对界面Y轴坐标进行动态设置来实现的,这里面也没啥好说的,都是基本的手势操作。
同时,当移动到一定位置,但是还没有到达我们指定的位置时,需要对其进行回弹处理,而回弹操作是在onTouchEvent的重载方法实现的。当检测到手指松开时,通过当前所处的位置和我们定义的区间进行对比,来判断View应该回弹到哪个状态。
* toTop()恢复到顶部状态
* toCenter() 恢复到中间状态
* toBottom() 恢复到底部状态

游戏截图旋转实现

移动已经实现了,继续观察当初的界面,当可移动的布局从中间状态移动到底部状态时,处于最底层的游戏截图会进行旋转,当其从底部返回到中间时,恢复原始状态。
代码如下:
用于显示游戏截图的Fragment。

public class ScreenshotFragment extends BaseFragment {
    @InjectView(R.id.img_banner)
    ImageView mBannerImg;
    private BannerEntity mEntity;
    private boolean isCanClick = false;
    private HandlerThread mHt;
    private RotationHandler mHandler;

    private ScreenshotFragment() {

    }

    public static ScreenshotFragment newInstance(BannerEntity entity) {
        ScreenshotFragment fragment = new ScreenshotFragment();
        Bundle b = new Bundle();
        b.putParcelable("entity", entity);
        fragment.setArguments(b);
        return fragment;
    }

    @Override
    protected void init(Bundle savedInstanceState) {
        mEntity = getArguments().getParcelable("entity");
        mBannerImg.setScaleType(ImageView.ScaleType.FIT_XY);
        setUpData(mEntity);
        mRootView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!isCanClick) {
                    return;
                }
            }
        });
        mHt = new HandlerThread("rotation_ht", Process.THREAD_PRIORITY_DEFAULT);
        mHt.start();
        mHandler = new RotationHandler(mHt.getLooper());
    }

    /**
     * 获取ImageView
     */
    public ImageView getBannerImg() {
        return mBannerImg;
    }

    /**
     * 设置能否点击
     */
    public void setCanClick(boolean isCanClick) {
        this.isCanClick = isCanClick;
    }

    /**
     * 设置数据
     *
     * @param entity
     */
    private void setUpData(BannerEntity entity) {
        mBannerImg.setTag(null);
        Glide.with(getContext()).load(entity.getImgUrl())
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .into(mBannerImg);
    }

    /**
     * 设置图片
     */

    public void setDrawable(@DrawableRes int drawable) {
        if (mBannerImg != null) {
            mBannerImg.setImageResource(drawable);
        }
    }

    /**
     * 更新数据
     */
    public void update(BannerEntity entity) {
        mEntity = entity;
        setUpData(entity);
    }

    /**
     * 设置Banner高度
     *
     * @param height
     */
    public void setBannerHeight(int height) {
        if (mBannerImg == null) {
            return;
        }
        ViewGroup.LayoutParams lp = mBannerImg.getLayoutParams();
        lp.height = height;
        mBannerImg.setLayoutParams(lp);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mHt != null) {
            mHt.quit();
        }
    }

    /**
     * 对图片进行旋转
     *
     * @param rotation 是否旋转
     */
    public void setRotation(boolean rotation) {
        setRotation(rotation, false);
    }

    /**
     * 对图片进行旋转
     *
     * @param useAnim 使用动画
     */
    public void setRotation(boolean rotation, boolean useAnim) {
        mHandler.obtainMessage(rotation ? 0 : 1, useAnim).sendToTarget();
    }

    @Override
    protected int setLayoutId() {
        return R.layout.fragment_banner;
    }

    private class RotationHandler extends Handler {
        public RotationHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mBannerImg == null) {
                return;
            }
            final int what = msg.what;
            final boolean useAnim = (boolean) msg.obj;
            mBannerImg.post(new Runnable() {
                @Override
                public void run() {
                    if (what == 0) {
                        rotation(mBannerImg, useAnim);
                    } else if (what == 1) {
                        resumeRotation(mBannerImg, useAnim);
                    }
                }
            });
        }

        /**
         * 旋转
         *
         * @param img
         */
        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);
            }
        }

        /**
         * 恢复
         *
         * @param img
         */
        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);
            }
        }
    }
}

ScreenshotFragment 是用来展示游戏截图的界面,当进行移动时,需要执行以下操作:旋转–>移动到中间–>进行放大,这三个操作,通过属性动画,便能很容易实现其动画效果,需要注意的是,在进行放大的过程中,我们需要改变ImageView的宽和高,让ImagView能完整展示放大后的图片,同时为来保证UI更新的安全性,需要使用一个异步handler来实现其更新操作,我在这里是使用轻量级的HandlerThread来实现这异步更新UI的操作。

/**
     * 初始化游戏截图ViewPager
     *
     * @return
     */
    private void setupGameShotVp(final ViewPager viewPager) {
        SimpleViewPagerAdapter adapter = new SimpleViewPagerAdapter(getSupportFragmentManager());
        List     data    = getBannerData();
        for (BannerEntity entity : data) {
            adapter.addFrag(ScreenshotFragment.newInstance(entity), "");
        }
        viewPager.setAdapter(adapter);
        viewPager.setOffscreenPageLimit(data.size());
        mIndicator.setViewPager(viewPager);
//        mIndicator.onPageSelected(0);
        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);
                            }
                        }
                    }
                });
            }
        });
    }

上面是初始化游戏截图的代码,在初始位置时,需要固定ViewPage的高度,旋转完毕后,需要将ViewPage高度设置为屏幕的高度,这样,才能保证游戏截图能被完全显示。

现在的效果
这是我们基本完成来的效果,已经越来越接近于当乐的游戏详情页面来,但是还不能进行相应的事件响应,接下来就是处理最具挑战的滑动冲突和事件分发了。想想我还是有点小激动呢…..

用户评论