封面图.png

能有一个双休的周末,对于程序员来说,也算是一件幸福的事情吧。苦逼的加了一周的班,终于可以休息放松放松了。作为一个LOL爱好者,周末最开心的事当然就是约上几个小伙伴一起开黑了。一起超神、一起连跪,也算是周末的一大乐事。这几天英雄联盟搞活动,抽到一个安妮限定皮肤,可把我乐坏了,于是马上就登陆掌盟客户端查看皮肤。进入皮肤浏览界面之后,觉得这个皮肤浏览的效果还真不错,如下图:

掌盟皮肤 浏览效果.gif

作为一个程序员,当然第一时间就是思考它是怎么实现的?我能用什么方法来实现类似的效果?于是花了半天的时间,做了一个类似的效果。因此本篇文章就分享一下如何实现这一效果。最后实现的效果如下:

仿掌盟皮肤 浏览效果.gif

思路与分析

在开始写代码之前,我们还是来分析一下界面元素,和该用什么技术来实现各个部分。

1,首先是整个界面的滑动,我们肯定一眼就能看出来,用ViewPager 实现。
2,ViewPager 滑动时有放大缩小的动画,用ViewPager.Transfoemer 轻松搞定。
3,ViewPager 显示多页(展示前后页面的部分)。
4,界面图片的形状,旋转90度的等腰梯形。这个只能用自定义View来实现了。
5,整个界面的背景为当前显示图片的高斯模糊图。

代码实现

上面分析了界面的构成元素,那么现在我们就来看一下具体的实现。

1, ViewPager 展示多页
这个问题在我们前一篇文章已经讲过,这里不再重复,就是用ViewGroup 的 clipChildren 属性,值为false。也就是在整个布局的跟节点添加下面一行代码:

android:clipChildren="false"

然后,ViewPager需要设置左右Margin,也就是前后页显示的位置

<android.support.v4.view.ViewPagerandroid:id="@+id/my_viewpager"android:layout_width="wrap_content"android:layout_height="300dp"android:clipChildren="false"android:layout_marginLeft="50dp"android:layout_marginRight="50dp"android:layout_centerInParent="true"/>

从上面的效果图可以看到,当前页和前后页的部分是有间距的,我们只需要在Item布局中左右添加margin属性:

 android:layout_marginLeft="30dp"android:layout_marginRight="30dp"

好了,这样ViewPager就能显示多页,并且当前页和前后页之间还有一定的间距。

2, ViewPager 切换时的动画
ViewPager 切换时的自定义动画用ViewPager.PageTransformer, 这个在上一篇文章也讲过,没看过的倒回去看一下。这里不细讲了,直接上代码:

public class CustomViewPagerTransformer implements ViewPager.PageTransformer {private int maxTranslateOffsetX;private ViewPager viewPager;private static final float MIN_SCALE = 0.75f;public CustomViewPagerTransformer(Context context) {this.maxTranslateOffsetX = dp2px(context, 160);}public void transformPage(View view, float position) {// position的可能性的值有,其实从官方示例的注释就能看出://[-Infinity,-1)  已经看不到了// (1,+Infinity] 已经看不到了// [-1,1]// 而我们从写PageTransformer,操作View动画的重点区间就在[-1,1]if (viewPager == null) {viewPager = (ViewPager) view.getParent();}int leftInScreen = view.getLeft() - viewPager.getScrollX();int centerXInViewPager = leftInScreen + view.getMeasuredWidth() / 2;int offsetX = centerXInViewPager - viewPager.getMeasuredWidth() / 2;float offsetRate = (float) offsetX * 0.38f / viewPager.getMeasuredWidth();float scaleFactor = 1 - Math.abs(offsetRate);if (scaleFactor > 0) {view.setScaleX(scaleFactor);view.setScaleY(scaleFactor);view.setTranslationX(-maxTranslateOffsetX * offsetRate);}}/*** dp和像素转换*/private int dp2px(Context context, float dipValue) {float m = context.getResources().getDisplayMetrics().density;return (int) (dipValue * m + 0.5f);}
}

3, 自定义多边形ImageView
多边形ImageView,我们通过自定义的方式实现,继承ImageView, 然后重写onDraw()方法。这里实现这种不规则的多边形View有两种方法。第一:使用PorterDuffXfermode,这种方法需要你给一个蒙板图片,在onDraw 方法中,先绘制蒙板图片,然后设置Paint的setXfermode 为PorterDuff.Mode.SRC_IN,再绘制要显示的图片,这样就能把显示的图片裁剪成蒙板的形状。第二: 使用canvas的clipPath() 方法,我们用Path 来绘制多边形,然后clipPath() 将画布裁剪成绘制的形状,然后在绘制要显示的图片。

关于PorterDuffXfermode 的更多用法,有兴趣的可以去google 一下,网上有很多相关的文章。这里我用的是两种方法的结合,先用clipPath得到一个需要形状的bitmap,然后使用PorterDuffXfermode。自定义View代码如下:

public class PolygonView extends AppCompatImageView {private int mWidth = 0;private int mHeight = 0;private Paint mPaint;private Paint mBorderPaint;private PorterDuffXfermode mXfermode;private Bitmap mBitmap;private int mBorderWidth;private Bitmap mMaskBitmap;public PolygonView(Context context) {super(context);init();}public PolygonView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}public PolygonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init(){mBorderWidth = DisplayUtils.dpToPx(4);setLayerType(View.LAYER_TYPE_SOFTWARE, null);// 关闭硬件加速加速mPaint = new Paint();mPaint.setAntiAlias(true);mPaint.setColor(Color.RED);mPaint.setDither(true);mBorderPaint = new Paint();mBorderPaint.setColor(Color.WHITE);mBorderPaint.setStyle(Paint.Style.FILL_AND_STROKE);mBorderPaint.setAntiAlias(true);//抗锯齿mBorderPaint.setDither(true);//防抖动mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);mWidth = getMeasuredWidth();mHeight = getMeasuredHeight();mMaskBitmap = getMaskBitmap();}@Overridepublic void setImageResource(@DrawableRes int resId) {super.setImageResource(resId);mBitmap = BitmapFactory.decodeResource(getResources(),resId);invalidate();}@Overrideprotected void onDraw(Canvas canvas) {canvas.save();canvas.drawBitmap(mMaskBitmap,0,0,mBorderPaint);mPaint.setXfermode(mXfermode);Bitmap bitmap = getCenterCropBitmap(mBitmap,mWidth,mHeight);canvas.drawBitmap(bitmap,0,0,mPaint);mPaint.setXfermode(null);canvas.restore();}private Bitmap getMaskBitmap(){Bitmap bm = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);Canvas c = new Canvas(bm);Point point1 = new Point(0,30);Point point2 = new Point(mWidth,0);Point point3 = new Point(mWidth,mHeight);Point point4 = new Point(0,mHeight - 30);Path path = new Path();path.moveTo(point1.x,point1.y);path.lineTo(point2.x,point2.y);path.lineTo(point3.x,point3.y);path.lineTo(point4.x,point4.y);path.close();c.drawPath(path,mBorderPaint);return bm;}/*** 对原图进行等比裁剪*/private Bitmap scaleImage(Bitmap bitmap){if(bitmap!=null){int widht=bitmap.getWidth();int height=bitmap.getHeight();int new_width=0;int new_height=0;if(widht!=height){if(widht>height){new_height=mHeight;new_width=widht*new_height/height;}else{new_width=mWidth;new_height=height*new_width/widht;}}else{new_width=mWidth;new_height=mHeight;}return Bitmap.createScaledBitmap(bitmap, new_width, new_height, true);}return null;}private Bitmap getCenterCropBitmap(Bitmap src, float rectWidth, float rectHeight) {float srcRatio = ((float) src.getWidth()) / src.getHeight();float rectRadio = rectWidth / rectHeight;if (srcRatio < rectRadio) {return Bitmap.createScaledBitmap(src, (int)rectWidth, (int)((rectWidth / src.getWidth()) * src.getHeight()), false);} else {return Bitmap.createScaledBitmap(src, (int)((rectHeight / src.getHeight()) * src.getWidth()), (int)rectHeight, false);}}}

建议:这里使用clipPath方法的时候,会出现很多锯齿,即使Paint 设置了抗锯齿也没啥用,所以建议使用PorterDuffXfermode 方法。要实现类似的效果,最好是找设计师要一张蒙板形状图。在用PorterDuffXfermode实现,简单效果好。

通过上面的3步,其实整个 界面的效果差不多已经出来了,最后我们需要做的就是高斯模糊背景图。

4, 背景图高斯模糊
背景的高斯模糊就很简单了,前面我也有写过关于几种高斯模糊方法的对比(Android 图片高斯模糊解决方案),最后封装了一个方便的库(https://github.com/pinguo-zhouwei/EasyBlur),只需要简单几行代码就行。我们在ViewPager的onPageSelect方法中,获取显示的图片,进行高斯模糊处理。

      @Overridepublic void onPageSelected(int position) {Bitmap source = BitmapFactory.decodeResource(getResources(),VPAdapter.RES[position]);Bitmap bitmap = EasyBlur.with(getApplicationContext()).bitmap(source).radius(20).blur();mImageBg.setImageBitmap(bitmap);mDesc.setText(mVPAdapter.getPageTitle(position));}

最后,给出完整的布局文件和Activity代码:

1, activity布局文件:

<?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:orientation="vertical"android:clipChildren="false"android:layout_width="match_parent"android:layout_height="match_parent"><!-- 高斯模糊背景--><ImageViewandroid:id="@+id/activity_bg"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="centerCrop"/><!-- Toolbar--><RelativeLayoutandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="50dp"><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/navigation_back_white"android:layout_centerVertical="true"android:layout_marginLeft="15dp"/><TextViewandroid:id="@+id/title_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:textSize="18sp"android:textColor="@android:color/white"/></RelativeLayout><android.support.v4.view.ViewPagerandroid:id="@+id/my_viewpager"android:layout_width="wrap_content"android:layout_height="300dp"android:clipChildren="false"android:layout_marginLeft="50dp"android:layout_marginRight="50dp"android:layout_centerInParent="true"/><com.zhouwei.indicatorview.CircleIndicatorViewandroid:id="@+id/indicatorView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_marginBottom="60dp"android:layout_centerHorizontal="true"app:indicatorSelectColor="#C79EFE"app:indicatorSpace="5dp"app:indicatorRadius="8dp"app:enableIndicatorSwitch="false"app:indicatorTextColor="@android:color/white"app:fill_mode="number"app:indicatorColor="#C79EFE"/><TextViewandroid:id="@+id/skin_desc"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:layout_below="@+id/my_viewpager"android:layout_marginTop="20dp"android:textColor="@android:color/white"android:textSize="18sp"/>
</RelativeLayout>

2, Activity代码:

public class ViewPagerActivity extends AppCompatActivity {private ViewPager mViewPager;private VPAdapter mVPAdapter;private ImageView mImageBg;private CircleIndicatorView mCircleIndicatorView;private TextView mTitle,mDesc;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.viewpager_transform_layout);View view = findViewById(R.id.toolbar);StatusBarUtils.setTranslucentImageHeader(this, 0,view);initView();}private void initView() {mViewPager = (ViewPager) findViewById(R.id.my_viewpager);mImageBg = (ImageView) findViewById(R.id.activity_bg);mCircleIndicatorView = (CircleIndicatorView) findViewById(R.id.indicatorView);mTitle = (TextView) findViewById(R.id.title_name);mDesc = (TextView) findViewById(R.id.skin_desc);mTitle.setText("黑暗之女");mViewPager.setPageTransformer(false,new CustomViewPagerTransformer(this));// 添加监听器mViewPager.addOnPageChangeListener(onPageChangeListener);mVPAdapter = new VPAdapter(getSupportFragmentManager());mViewPager.setAdapter(mVPAdapter);mViewPager.setOffscreenPageLimit(3);//  Indicator 和ViewPager 建立关联mCircleIndicatorView.setUpWithViewPager(mViewPager);// 首次进入展示第二页mViewPager.setCurrentItem(1);}@Overridepublic boolean onTouchEvent(MotionEvent event) {return mViewPager.onTouchEvent(event);}private ViewPager.OnPageChangeListener onPageChangeListener = new ViewPager.OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Overridepublic void onPageSelected(int position) {Bitmap source = BitmapFactory.decodeResource(getResources(),VPAdapter.RES[position]);Bitmap bitmap = EasyBlur.with(getApplicationContext()).bitmap(source).radius(20).blur();mImageBg.setImageBitmap(bitmap);mDesc.setText(mVPAdapter.getPageTitle(position));}@Overridepublic void onPageScrollStateChanged(int state) {}};
}

ViewPager的每一个页面用Fragment 来展示的,Fragment代码如下:

public class ItemFragment extends Fragment {private PolygonView mPolygonView;public static ItemFragment newInstance(int resId){ItemFragment itemFragment = new ItemFragment();Bundle bundle = new Bundle();bundle.putInt("resId",resId);itemFragment.setArguments(bundle);return itemFragment;}@Nullable@Overridepublic View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {View view = inflater.inflate(R.layout.view_pager_muti_layout,null);mPolygonView = (PolygonView) view.findViewById(R.id.item_image);// 做一个属性动画ObjectAnimator animator = ObjectAnimator.ofFloat(mPolygonView,"rotation",0f,10f);animator.setDuration(10);animator.start();return view;}@Overridepublic void onViewCreated(View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);int resId = getArguments().getInt("resId");mPolygonView.setImageResource(resId);// 设置图片}
}

说明:在Fragment中对PolygonView做了一个旋转的动画,是因为PolygonView 是一个竖着的等腰梯形,但是看效果图,其实不是,还有一个小幅度的旋转,如果将这个旋转放在PolygonView 里面做的话,发现每次ViewPager 切换的时候,都有一个旋转动画,效果不好,因此将动画放在这里。应该还有其他更优雅一点的方法,有兴趣的可以去试一下。

ViewPager系列之-仿掌上英雄联盟皮肤浏览效果相关推荐

  1. 仿掌上英雄联盟能力值分析效果

    今日科技快讯 就在上周五,百度又出现重磅新闻,副总裁李明远被人举报贪污,在公司收购的项目中与被收购公司私下里有巨额经济来往.李明远虽然回应称自己没有贪污,是被人诬陷,但目前已引咎辞职.这已经是百度今年 ...

  2. android开发自定义View(四)仿掌上英雄联盟能力值分析效果

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 原始图效果 模仿效果 PNG GIF 流程 绘制中心线,用于计算外层多边形各点的坐标 绘制最外层多边形 分析原型图算出每个多边形之间 ...

  3. Android仿掌上英雄联盟首页,实现折叠效果

    不单单是掌上英雄联盟,像微博发现页也用了这样的布局,当滑动到一定距离的时候,自动隐藏轮播图,或者标题栏下面的布局.并且使tablayout置顶. 与之相似的还有简书的个人页面也是这样的布局. 图片处理 ...

  4. 仿掌上英雄联盟云顶之弈 - 微信小程序版

    毕业3年已经很少玩英雄联盟了,最近出了个云顶之弈的新模式,就玩了几局.自己还没有写过微信小程序,正好就仿照掌上英雄联盟写个Demo,并没有使用很多技巧,算是初步学习的测验吧. github源码 欢迎给 ...

  5. 仿掌上英雄联盟能力值图形绘制

    一,前沿 相信玩撸啊撸的撸友们一定记得掌上英雄联盟App的能力值吧~~ 好吧,不记得没关系我来给大家上张图! ! 所以今天呢我就抽出时间模仿了一下: 照例线来张GIF,有图有真相 以下是我的微博账号希 ...

  6. 用Kotlin画起仿掌上英雄联盟和懂球帝的自定义多边形战力图

    大家好,可能今年换了公司过得比较安逸,自己的心情也不算太好,所以一段时间都没很好的学习和更新文章.自从最近看了扔物线HenCoder的文章之后,自己感觉自己对很多自定义View和动画的知识都不太懂,甚 ...

  7. 所向无前 正当年!掌上英雄联盟S10换肤设计小结

    项目背景 掌盟作为官方APP,会在重要节点配合产品和运营侧进行换肤设计,渲染节点氛围,从而希望可以提升产品口碑,做一个有温度的APP. 在掌盟的换肤工作中,可以总结为3种换肤类型: S10换肤的工作就 ...

  8. 【无限互联】 学院作品: LOL掌上英雄联盟

    一.项目介绍: 我做的项目是LOL英雄掌上联盟(模仿版),是腾讯游戏英雄联盟官方制作的app,来为LOL玩家提供最专业的资讯和社交服务 ,可以查询英雄联盟的最新的情况,这款app的功能有:       ...

  9. ios自定义UITabBar-仿写掌上英雄联盟的UITabBar

    最近在仿写这个软件,发现UITabBar不是使用的原生的.所以,需要自己去自定义 观察掌盟的UITabBar 观察官方的软件下面的tabBar,我们可以发现几点需要我们注意的 1. 当select一个 ...

最新文章

  1. Guava库学习:学习Guava EventBus(二)EventBus 事件订阅示例
  2. 支付宝安全进程也“耍流氓”
  3. C++ 学习基础篇(一)—— C++与C 的区别
  4. oracle ash介绍,Oracle ---- 性能调查之ASH(一)
  5. Qt_数据库基本操作(未完成)
  6. useradd和adduser的区别
  7. [Python] numpy库的简介和常用函数
  8. java day41【JSP 、MVC开发模式 、EL表达式 、JSTL标签 、三层架构】
  9. sql语句分页多种方式ROW_NUMBER()OVER
  10. 使用 URLDecoder 和 URLEncoder 对中文字符进行编码和解码
  11. 电磁干扰、电磁兼容性和电磁屏蔽区别及联系
  12. 【Python数据挖掘课程】八.关联规则挖掘及Apriori实现购物推荐
  13. 2015,了不起的邮件创意!
  14. 如何把微信删除的聊天记录恢复正常?你知道吗
  15. 大型园区网络解决方案-IBN
  16. 行人重识别-视频重识别
  17. 余世维 - 老板首先要诚实
  18. 微型计算机赛睿寒冰5评测,为什么说寒冰5才是赛睿性价比最高的游戏耳机?
  19. Java对List集合进行排序
  20. 网络安全 - 信息收集

热门文章

  1. 百度发春节搜索大数据:80后最关注什么?
  2. python实战|爬取1000位小姐姐私房照制作照片墙,刷新你三观的颜值!
  3. ESXI虚拟机下直通外部USB硬盘给黑群晖当硬盘
  4. Parcelable的用法及记录一个Parcelable遇到的坑
  5. 攻防世界—MISC 新手区1-12
  6. mysql optimize原理_MySQL数据库入门:表的Optimize 优化
  7. Linux中的如何对当前进程进行查看和监控——ps命令和pgrep命令
  8. 高可用(keepalived)部署方案
  9. PGSQL修改字段类型-数字类型转换成时间格式
  10. HTML:基础网页开发语言