編輯:關於Android編程
通過上一篇文章的分析,基本已經了解當樂游戲詳情頁面的思想思路了,本篇文章主要是實現頁面的基本效果。
通過上一篇文章分析,已經知道,當樂游戲詳情頁是通過3個不同層次的布局進行疊加來實現的,為了實現這種層次結構,需要用到RelativeLayout 。
這3個View層次如圖所示,分別為:介紹游戲簡介的頭布局、介紹游戲詳情的詳情界面、還有toolbar。
如圖所示,紅色圈圈裡面的便是介紹這個游戲的頭布局。
layout_game_detail_head.xml
如圖所示,黃色圈圈裡面的是展示游戲相親的內容布局。
layout_content.xml
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的高度) /**通過手勢控制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高度設置為屏幕的高度,這樣,才能保證游戲截圖能被完全顯示。
這是我們基本完成來的效果,已經越來越接近於當樂的游戲詳情頁面來,但是還不能進行相應的事件響應,接下來就是處理最具挑戰的滑動沖突和事件分發了。想想我還是有點小激動呢…..
前陣子要的工作是給桌面(Launcher啟動器,其實也是一個activity)添加一個觸摸特效(一個View),而這個特效是每次觸碰都會有,不管你在桌面上做什麼操作都會顯
GLSurfaceView提供了下列特性:1> 管理一個surface,這個surface就是一塊特殊的內存,能直接排版到android的視圖view上。2>
關於Android M(6.0)新的授權方式只有我們設置app的targetSdkVersion為23,並且app運行在6.0之後的設備才使用新的權限系統。在Androi
對話框 Dialog什麼是對話框對話框是在當前的頁面之上彈出的小窗口, 用於顯示一些重要的提示信息, 提示用戶的輸入,確認信息,或顯示某種狀態.如 : 顯示進度條對話框,