在上一篇文章里面,基本上算是实现了该效果的布局,有了布局,接下来就要对布局进行移动处理。
android 仿当乐游戏详情页面(一)
android 仿当乐游戏详情页面(二)
android 仿当乐游戏详情页面(三)
对于移动的分析
通过第一篇文章的分析,在所有控件里面,能移动的只有用于展示游戏简介和游戏相关数据的View,并且该View的移动有以下三种状态:
处于顶部的状态
中间状态
底部状态:
如上面几张图片所示,处于顶部状态,TabLayout 悬停在Toolbar的下面,而此时,用于介绍游戏简介的View被移出布局;处于中间状态时,Toolbar变为全透明状态,当位于底部时,用于展示游戏简介的View被固定在底部,其它的内容将被移出界面之外。
位置状态
为了便于理解,首先像定义几个字段。
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正下方;
为了将mGInfoView移除界面之外,mContentView需要将Y坐标移动到-mHeadH + mBarH
的位置。
因此mTolL = -mHeadH + mBarH
中间状态分析
对于中间状态,便简单多了,中间状态时,mContentView只需要将Y坐标往下移动到任一位置即可,此时,Toolbar处于完全透明状态。
这里,我是将它往下距离它基准位置的150dp 的位置。
因此mCenterL = Util.dp2px(150)
底部状态分析
当mContentView处于底部状态,mGInfoView将被固定在屏幕底部,其它的内容将被除出界面之外。
通过分析,很容易知道:mBottomL = mScreenH - mStateBarH - mNBarH - mHeadH + mBarH
代码实现
现在,3种状态算是分析完成了,接下来便是代码的编写。
在android 里面,对控件的移动操作,首先想到的是使用手势。同时,在手势移动的过程中,还需要对ToolBar进行透明度处理。
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个基准位置!这样的操作很不符合用户体验,并且也没有达到三个状态的要求。
因此,我们需要定义几个阀值,当手指离开屏幕的时候,mContentView可以根据这几个阀值来判断它应该回归到具体哪个基准位置。
阀值的定义如下:
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°同时会填充整个屏幕。
在这里吐槽一下,看起来这种效果不难实现,但是等真正开发时会出现各种各样的坑,说多了都是泪,谁做谁知道 :( !!!翻遍了这个stackoverflow都没有好的解决方案,最终研究出来使用属性动画是最简单实现并且性能是最好的!!
为了便于理解,将定义一个字段 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旋转了,但只显示了一截,另一节被“吃掉了”。
出现这个问题的原因是:在对图片进行旋转的过程中,属性动画已经改变了mBannerImg的高度参数。比如在mContentView处于底部状态时,mBannerImg的高度已经变为屏幕的高度,但是作为Fragment容器的mImgShotView的高度还是没有被改变;这就导致刚才所说的那个问题。
知道了原因,解决就简单了,对mBannerImg进行操作前,只需要将mImgShotView的参数修改为与mBannerImg的参数一致便可,代码如下所示:
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) {} }); 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); } } } }); } }); }
|
最终的效果
现在布局的移动和截图旋转算是完成了,接下来便是需要解决最困难的事件分发!!
### [Android 仿当乐游戏详情页(三)](http://www.jianshu.com/p/75212c876d53)