最近产品经理出了一个幺蛾子,说要通过一个小游戏来吸引用户获取奖励,当时过需求的时候,内心何止是一万个草泥马奔腾而过,简直是一亿个草泥马。过需求之后就冷静下来讨论该怎么实现,做完之后发现,其实也没有那么难!总的效果如下:
因为csnd上传最大不能超过2M所以把gif图放到github上了
点击查看效果图
1.小人走动效果
2.路线布局
3.小人走动的四个方向
4.动画效果:红包小人矿山铲子这是一类。前进后退踩雷奖励骰子次数这是一类。替换场景是单独的动画效果。
这里使用的是drawBitmap配合invalidate来实现一个动态走动的效果。小人每一个方向都是4张图片,把这4张图片放到一个一维数组中,每次draw的时候需要的bitmap都从数组里面取。代码如下:
public GameAnimation mPersonAnim[] = new GameAnimation[ANIM_COUNT];
public void initAnimation(Context context) {
// 这里可以用循环来处理总之我们需要把动画的ID传进去
mPersonAnim[ANIM_DOWN] = new GameAnimation(context, new int[] { R.mipmap.img_hero_down_a, R.mipmap.img_hero_down_b, R.mipmap.img_hero_down_c, R.mipmap.img_hero_down_d }, true);
mPersonAnim[ANIM_LEFT] = new GameAnimation(context, new int[] { R.mipmap.img_hero_left_a, R.mipmap.img_hero_left_b, R.mipmap.img_hero_left_c, R.mipmap.img_hero_left_d }, true);
mPersonAnim[ANIM_RIGHT] = new GameAnimation(context, new int[] { R.mipmap.img_hero_right_a, R.mipmap.img_hero_right_b, R.mipmap.img_hero_right_c, R.mipmap.img_hero_right_d }, true);
mPersonAnim[ANIM_UP] = new GameAnimation(context, new int[] { R.mipmap.img_hero_up_a, R.mipmap.img_hero_up_b, R.mipmap.img_hero_up_c, R.mipmap.img_hero_up_d }, true);
}
public void DrawAnimation(Canvas Canvas, Paint paint, int x, int y) {
// 如果没有播放结束则继续播放
if (!mIsend) {
Canvas.drawBitmap(mframeBitmap[mPlayID], x, y, paint);
long time = System.currentTimeMillis();
if (time - mLastPlayTime > ANIM_TIME) {
mPlayID++;
mLastPlayTime = time;
if (mPlayID >= mFrameCount) {
// 标志动画播放结束
mIsend = true;
if (mIsLoop) {
// 设置循环播放
mIsend = false;
mPlayID = 0;
}
}
}
}
}
这个走动效果是根据大神博客来实现的。
看效果图,这是一个写死的不规则的布局,当时完全没有任何思路,后来讨论需求的时候说,总共15个场景,每个场景的路线都写死,并且都是5x6的格式,瞬间就明白使用什么方式了。没错,就是用recyclerview+GridLayoutManager来实现的,因为每一个场景服务端都会返回一个集合,每个单元格服务端都会告诉我们row和col,这样,我们布局起来就so easy了。总的一菊(句)花(话)就是:该显示的显示,不该显示的设置为gone。
@Override protected void mOnBindViewHolder(GameKingRingViewHolder holder, int position) {
GameCellBean gameCellBean = list.get(position);
if (gameCellBean == null) {
return;
}
updateBlockBg(holder.idIvBlock);// 不同的背景,单元格的背景和颜色也不同
if (gameCellBean.row != 0 && gameCellBean.col != 0) {
holder.itemView.setVisibility(View.VISIBLE);
} else {
holder.itemView.setVisibility(View.GONE);
}
}
小人要根据布局中的路线去走动,可是布局中的格子是打乱的,谁也不知道下一个格子是向下还是向上(不同的方向,小人的图片不一样)。这里也可以通过服务端返回的row和col来判断,举个栗子,当前小人所在坐标row和col分别为2,2,假设下一个格子的row和col是2,1,那么小人下一步的方向是向左。假设下一个格子的row和col是1,2,那么小人下一步的方向是向上。具体代码如下
private void setPersonForwardDirectionAndSetCurrentGameBean() {
if (currentPosition + 1 >= gameCellBeanList.size()) { // 替换场景
sendMsgForwardToChangeScene();
return;
}
// 例如当前currentGameBean的rowX=5,columnY=2
GameCellBean frontGameBean = gameCellBeanList.get(currentPosition + 1);
if (currentGameCellBean.row > frontGameBean.row) { // 向上换行 rowX为5,需要换成4
if (currentGameCellBean.col == frontGameBean.col) {
currentPosition++;
mAnimationState = KingRingPersonAnimation.ANIM_UP;
setCurrentGameBean(frontGameBean);
}
} else if (currentGameCellBean.row == frontGameBean.row) { // 判断左右方向 currentGameBean.columnY=0--frontGameBean.columnY=1
if (frontGameBean.col > currentGameCellBean.col) { // 向右
currentPosition++;
mAnimationState = KingRingPersonAnimation.ANIM_RIGHT;
setCurrentGameBean(frontGameBean);
} else if (frontGameBean.col < currentGameCellBean.col) { // 向左
currentPosition++;
mAnimationState = KingRingPersonAnimation.ANIM_LEFT;
setCurrentGameBean(frontGameBean);
}
} else if (currentGameCellBean.row < frontGameBean.row) { // 向下
if (currentGameCellBean.col == frontGameBean.col) {
currentPosition++;
mAnimationState = KingRingPersonAnimation.ANIM_DOWN;
setCurrentGameBean(frontGameBean);
}
}
}
public void startAnimProps(final int position, int propsType) {// 获取红包小人矿山铲子的动画效果
int[] location = new int[2];
if (propsType == GameKingRingAdapter.PROPS_RED_TOKEN || propsType == GameKingRingAdapter.PROPS_RED_DYNAMIC) {
idIvRed.getLocationInWindow(location);
} else if (propsType == GameKingRingAdapter.PROPS_PERSON || propsType == GameKingRingAdapter.PROPS_SHOVEL || propsType == GameKingRingAdapter.PROPS_MOUNTAIN) {
idIvMerge.getLocationInWindow(location);
}
int[] location2 = new int[2];
GameKingRingViewHolder viewHolder = (GameKingRingViewHolder) idRecyclerview.findViewHolderForAdapterPosition(position);
viewHolder.idIvProps.getLocationInWindow(location2);
int disX = location2[0] - location[0];
int disY = location2[1] - location[1];
ImageView imageView = new ImageView(this);
imageView.setImageDrawable(viewHolder.idIvProps.getDrawable());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(location2[0], location2[1], 0, 0);// - viewHolder.idIvProps.getTop()
idFlOtherContent.addView(imageView, params);
idFlOtherContent.setVisibility(View.VISIBLE);
viewHolder.idIvProps.setVisibility(View.INVISIBLE);
imageView.animate().translationX(-disX).translationY(-disY).setDuration(1000).setListener(new Animator.AnimatorListener() {
@Override public void onAnimationStart(Animator animation) {
notifyItemChanged(position);
}
@Override public void onAnimationEnd(Animator animation) {
idFlOtherContent.removeAllViews();
}
@Override public void onAnimationCancel(Animator animation) {}
@Override public void onAnimationRepeat(Animator animation) {}
}).start();
}
public void startRewardBackAnim(int position, final int value) { // 前进或后退效果,踩雷和奖励骰子次数跟这个一样
GameKingRingViewHolder gameKingRingViewHolder = (GameKingRingViewHolder) idRecyclerview.findViewHolderForAdapterPosition(position);
ImageView idIvProps = gameKingRingViewHolder.idIvProps;
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(idIvProps, "alpha", 1, 0, 1, 0, 1, 0, 1);
alphaAnim.setDuration(2000);
alphaAnim.addListener(new Animator.AnimatorListener() {
@Override public void onAnimationStart(Animator animation) {
if (!animationStartFlag) {
animationStartFlag = true;
}
}
@Override public void onAnimationEnd(Animator animation) {
if (animationStartFlag) {
animationStartFlag = false;
idIvPerson.removeLastStopCellIndex();
idIvPerson.setBackStep(value);
}
}
@Override public void onAnimationCancel(Animator animation) {}
@Override public void onAnimationRepeat(Animator animation) {}
});
alphaAnim.start();
}
public void updateSceneUIBySceneIndex(final String scenesName, final boolean forwardOrBack) { // 切换场景
if (TextUtils.isEmpty(scenesName)) {
return;
}
final ImageView imageView = new ImageView(this);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
imageView.setBackgroundDrawable(idLlContent.getBackground());
idFlOtherContent.addView(imageView, params);
idFlOtherContent.setVisibility(View.VISIBLE);
mGameKingRingAdapter.setScenesName(scenesName);
checkBg(scenesName);
if (forwardOrBack) {
mGameKingRingService.updateAdapterList();
}
idIvPerson.setVisibility(View.GONE);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(imageView, "translationY", 0, ActivityUtil.getScreenHeight(getApplication()));
alphaAnim.setDuration(1000);
alphaAnim.addListener(new Animator.AnimatorListener() {
@Override public void onAnimationStart(Animator animation) {
if (!animationStartFlag) {
animationStartFlag = true;
}
}
@Override public void onAnimationEnd(Animator animation) {
if (animationStartFlag) {
animationStartFlag = false;
idFlOtherContent.removeAllViews();
if (forwardOrBack) {
mGameKingRingService.updateSceneUIBySceneIndexCompletion();
}
}
}
@Override public void onAnimationCancel(Animator animation) {}
@Override public void onAnimationRepeat(Animator animation) {}
});
alphaAnim.start();
}
最后啰嗦一下,整个界面的布局是使用FrameLayout。Recyclerview是第一层,小人是第二层,所有的其他动画效果是第三层(都是通过动态的添加view,然后对view进行动画处理)。