标签:
通过上一篇文章的分析,基本已经了解当乐游戏详情页面的思想思路了,本篇文章主要是实现页面的基本效果。
android 仿当乐游戏详情页面(一)
通过上一篇文章分析,已经知道,当乐游戏详情页是通过3个不同层次的布局进行叠加来实现的,为了实现这种层次结构,需要用到RelativeLayout 。
这3个View层次如图所示,分别为:介绍游戏简介的头布局、介绍游戏详情的详情界面、还有toolbar。
如图所示,红色圈圈里面的便是介绍这个游戏的头布局。
layout_game_detail_head.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/head"
android:layout_width="match_parent"
android:layout_height="@dimen/game_detail_head_height">
<View
android:id="@+id/temp"
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="@color/transparent" />
<me.relex.circleindicator.CircleIndicator
android:id="@+id/indicator"
android:layout_width="match_parent"
android:layout_height="@dimen/game_detail_head_indicator_height"
android:gravity="center" />
<RelativeLayout
android:id="@+id/head_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/temp"
android:background="@color/white">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="130dp"
android:layout_marginTop="8dp"
android:text="游戏名"
android:textColor="@color/text_black_color"
android:textSize="@dimen/text_larger" />
<!--<com.lyy.ui.widget.StarBar-->
<!--android:id="@+id/star_bar"-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_alignLeft="@+id/name"-->
<!--android:layout_below="@+id/name"-->
<!--android:layout_marginTop="2dp" />-->
<TextView
android:id="@+id/detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/name"
android:layout_below="@+id/name"
android:text="角色扮演"
android:textColor="@color/text_gray_color"
android:textSize="@dimen/text_medium" />
<TextView
android:id="@+id/feature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/name"
android:layout_below="@+id/detail"
android:text="特性111111"
android:textColor="@color/text_gray_color"
android:textSize="@dimen/text_medium" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentBottom="true"
android:background="@color/line_color" />
</RelativeLayout>
<View
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_alignBottom="@+id/icon"
android:layout_alignParentRight="true"
android:visibility="gone" />
<ImageView
android:id="@+id/icon"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="10dp"
android:src="@mipmap/default_icon" />
</RelativeLayout>
如图所示,黄色圈圈里面的是展示游戏相亲的内容布局。
layout_content.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/game_detail_bar"
android:fitsSystemWindows="true"
android:orientation="vertical">
<include
layout="@layout/layout_game_detail_head"
android:layout_width="match_parent"
android:layout_height="@dimen/game_detail_head_height" />
<android.support.design.widget.TabLayout
android:id="@+id/tab"
style="@style/TabLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/line_color" />
<android.support.v4.view.ViewPager
android:id="@+id/content_vp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
layout_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/game_detail_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/tool_bar_height"
android:fitsSystemWindows="true">
<View
android:id="@+id/bar_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary" />
<TextView
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:gravity="center"
android:onClick="onClick"
android:paddingLeft="-5dp"
android:paddingRight="8dp"
android:text="test"
android:textColor="#fff"
android:textSize="16sp" />
<ImageView
android:id="@+id/download_manager"
style="@style/BarImgStyle"
android:layout_alignParentRight="true"
android:onClick="onClick"
android:scaleType="fitCenter"
android:src="@mipmap/icon_download" />
</RelativeLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:id="@+id/img_vp"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<include layout="@layout/layout_content"/>
<View
android:id="@+id/state_bar_temp"
android:layout_width="match_parent"
android:layout_height="@dimen/state_bar_height"
android:background="@color/colorPrimary"/>
<include
layout="@layout/layout_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/tool_bar_height"/>
</RelativeLayout>
这些都是一些常规的视图布局,通过在RelativeLayout里面对各个布局进行不同层次的摆放以达到实现复杂界面的效果。
观察当乐的游戏内容介绍,发现内容界面的移动有如下三种状态:
1、处于顶部的状态
2、中间状态
3、底部状态
处于顶部状态时,图一中,红色圈圈部分的游戏简介被移出布局之外,并且tab被固定在toobar下面。
如图二所示,当处于中间状态时,toolba完全透明,并且介意游戏各种详情的界面移动到中间,而当其处于底部状态时,由于展示游戏各种信息的布局被移出来界面之外,此时,游戏简介布局被固定在屏幕底部。
在移动的过程中,我们需要几个参数来定义移动布局几个状态所处的位置:
mTopL = -mHeadH + mBarH;
mCenterL = Util.dp2px(150);
mBottomL = mScreenH - mStateBarH - mNBarH - mHeadH + mBarH;
移动布局的实现代码如下:
/**通过手势控制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<BannerEntity> 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高度设置为屏幕的高度,这样,才能保证游戏截图能被完全显示。
这是我们基本完成来的效果,已经越来越接近于当乐的游戏详情页面来,但是还不能进行相应的事件响应,接下来就是处理最具挑战的滑动冲突和事件分发了。想想我还是有点小激动呢…..
标签:
原文地址:http://blog.csdn.net/qwe511455842/article/details/51001084