热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

写在前面在上一篇文章里面,基本上算是实现了该效果的布局,有了布局,接下来就要对布局进行移动处理。android仿当乐游戏详情页面(一)对于移动的分析通过第一篇文章的分析,

写在前面

在上一篇文章里面,基本上算是实现了该效果的布局,有了布局,接下来就要对布局进行移动处理。
android 仿当乐游戏详情页面(一)

对于移动的分析

通过第一篇文章的分析,在所有控件里面,能移动的只有用于展示游戏简介和游戏相关数据的View,并且该View的移动有以下三种状态:
1. 处于顶部的状态
顶部状态

  1. 中间状态
    中间状态

  2. 底部状态:
    底部状态

如上面几张图片所示,处于顶部状态,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 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 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的参数一致便可,代码如下所示:

/** * 初始化游戏截图ViewPager */
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);
    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 if (fragment != null) {
                            fragment.setBannerHeight(h);
                        }
                    }
                }
            });
        }
    });
}

最终的效果

最终效果


现在布局的移动和截图旋转算是完成了,接下来便是需要解决最困难的事件分发!!

Android 仿当乐游戏详情页(三)


推荐阅读
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 标题: ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
  • Java SE从入门到放弃(三)的逻辑运算符详解
    本文详细介绍了Java SE中的逻辑运算符,包括逻辑运算符的操作和运算结果,以及与运算符的不同之处。通过代码演示,展示了逻辑运算符的使用方法和注意事项。文章以Java SE从入门到放弃(三)为背景,对逻辑运算符进行了深入的解析。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • C语言注释工具及快捷键,删除C语言注释工具的实现思路
    本文介绍了C语言中注释的两种方式以及注释的作用,提供了删除C语言注释的工具实现思路,并分享了C语言中注释的快捷键操作方法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
author-avatar
飞舞的猫2502890283
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有