前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >动态生成分享图片

动态生成分享图片

作者头像
HelloVass
发布于 2018-09-12 02:21:19
发布于 2018-09-12 02:21:19
2K00
代码可运行
举报
文章被收录于专栏:Hellovass 的博客Hellovass 的博客
运行总次数:0
代码可运行

写在最前

本文描述了如何实现该需求的思路,代码可能不通用,但是该思路应该可以解决很多类似的需求…

需要分享的内容

  • 上半部分,1-4张图片
  • 下半部分,包含很多细小的东西,签名、用户名、用户头像、二维码图片…
  • ​…

实现原理

因为我的需要还是比较复杂的(主要来自于上半部分的排列规则),所以直接 extends 了 ViewGroup 来 layout 这些子 View,然后用数据填充这些布局,然后创建分享 Bitmap。

对 ViewGroup 里的元素做一个抽象

对自定义View(我们就叫它 DynamicShareView 好了),它关心的是如何拿到上半部分的 View下半部分的 View,所以这里定义一个 Adapter 接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface Adapter {

  /**
   * 获取封面个数
   */
  int getCoverCount();

  /**
   * 获取封面 View
   */
  View getImage(ViewGroup viewGroup, int position);

  /**
   * 获取底部 View
   */
  View getBottom(ViewGroup viewGroup);
}

对调用者来说,我们只需要调用 DynamicShareView 实例的 setAdapter 方法,然后将一个实现了 Adapter 接口的对象传给它,接着 DynamicShareView 会将这些 View 挨个添加到进来,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void setAdapter(@NonNull Adapter adapter) {

   mAdapter = adapter;

   removeAllViews(); // 移除所有子View

   // 添加图片
   int imageCount = Math.min(mAdapter.getCoverCount(), MAX_COUNT);
   for (int index = 0; index < imageCount; index++) {

     View image = mAdapter.getImage(this, index);
     addView(image);
     mImages.add(image);
   }

   // 添加底部
   View bottomLayout = mAdapter.getBottom(this);
   mBottomLayout = bottomLayout;
   addView(bottomLayout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
 }

这步完成之后,才是重头戏,我们需要自己来 measure 内部的子 View,计算出 DynamicShareView 的宽高。

测量高度&布局

这里的实现,emmmmmmmmm,复杂度和产品的需求成正比。栗如,我这里的复杂度主要来自上半部分的排列规则:

1.一张封面时,封面的宽高等于屏幕的宽度

2.两张封面时,封面的宽度等于屏幕的宽度,高度为屏幕宽度的一半,上下排列

3.三张封面时,封面的高度等于屏幕宽度的一半,前两张图片占一行,均分;第三张图片独占一行

4.// 省略…

怎么破,这里,我定义了一个 Rule 接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface Rule {

  void measureChildren(int screenWidth, int screenHeight, int spacing, View... children);

  void layoutChildren(int screenWidth, int screenHeight, int spacing, View... children);
}

然后定义5个子类来测量布局,如下图:

Rule1Impl 对应只有 1 个封面的情况,Rule2Impl 对应有 2 个封面的情况,以此类推。BottomRule 负责下半部分子 View测量和布局。当然,如果你喜欢,也可以写成一坨代码来 work。我们先来看下,onMeasure 方法长咋样吧。

onMeasure 步骤
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   super.onMeasure(widthMeasureSpec, heightMeasureSpec);

   int widthSize = MeasureSpec.getSize(widthMeasureSpec);

   int heightSize = MeasureSpec.getSize(heightMeasureSpec);
   int heightMode = MeasureSpec.getMode(heightMeasureSpec);

   mActualWidth = widthSize; // 宽度等于屏幕宽度

   switch (mImages.size()) {

     case 1:
       mRule1.measureChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(0));
       break;

     case 2:
       mRule2.measureChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(0),
           mImages.get(1));
       break;

     case 3:
       mRule3.measureChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(0),
           mImages.get(1), mImages.get(2));
       break;

     case 4:
       mRule4.measureChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(0),
           mImages.get(1), mImages.get(2), mImages.get(3));
       break;
   }

   // 测量底部高度
   mBottomRule.measureChildren(mActualWidth, mActualHeight, mSpacing, mBottomLayout);

   // 获得实际高度
   mActualHeight = mActualWidth + mBottomLayout.getMeasuredHeight();

   // 设置 ViewGroup 尺寸
   setMeasuredDimension(mActualWidth,
       heightMode == MeasureSpec.EXACTLY ? heightSize : mActualHeight);
 }
layout 步骤
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {

    if (mImages == null || mImages.isEmpty()) return;

    switch (mImages.size()) {

      case 1:
        mRule1.layoutChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(0));
        break;

      case 2:
        mRule2.layoutChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(0),
            mImages.get(1));
        break;

      case 3:
        mRule3.layoutChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(0), mImages.get(1),
            mImages.get(2));
        break;

      case 4:
        mRule4.layoutChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(0), mImages.get(1),
            mImages.get(2), mImages.get(3));
        break;
    }

    mBottomRule.layoutChildren(mActualWidth, mActualHeight, mSpacing, mBottomLayout);
  }

看上去是不是很干净呢23333!

静态 View 的布局&测量

这里和上半部分不同,不需要根据业务动态排列子 View,所以使用一个 xxxx.xml 来布局,如图:

NOTE:这里的根布局,没有写 layout_height=match_parent,因为下半部分的高度实际上应由内部的子 View 高度来决定!这也就是说,我们进行下半部分测量的时候,我们需要自己计算下半部分的高度到底是多少,这样,才能得到 DynamicShareView 的高度数值!那么问题来了,如何测量 layout_heigh=wrap_contentView的高度呢?看代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class BottomRule extends AbsRule {
  public BottomRule(Context context) {
    super(context);
  }

  @Override
  public void measureChildren(int screenWidth, int screenHeight, int spacing, View... children) {
    measureManually2(children[0], screenWidth);
  }

  @Override
  public void layoutChildren(int screenWidth, int screenHeight, int spacing, View... children) {
    children[0].layout(0, screenWidth, screenWidth, screenHeight);
  }
}

public abstract class AbsRule implements Rule {

  private Context mContext;

  public AbsRule(@NonNull Context context) {
    mContext = context;
  }

  protected void measureManually2(View target, int widthSize) {

    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(widthSize, View.MeasureSpec.EXACTLY);
    int heightMeasureSpec =
        View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
    target.measure(widthMeasureSpec, heightMeasureSpec);
  }
}

看到 measureManually2 这个方法,我想机智的童鞋已经豁然开朗了。至于为什么可以用这种方式测量出 layout_width=wrap_contentView 的高度,答案在 《Android开发艺术探索》一书中的 自定义View 一章。

implements Adapter 的坑

上面讲过只要实现 Adapter 这个接口就可以了,然而实际上,能否成功生成图片的关键也在这里,稍微不注意,就会陷入异步问题的深坑中。

如何加载图片呢

主流方案一般是用 Picasso、Glide 这样的图片加载库,这里,我使用的是 Glide。那直接 Glide.with().load().into ... 不就万事大吉了嘛!nonono,不是这样滴,因为 Glide 加载图片的过程是异步的,也就是说 ImageView 可能已经全部添加到 DynamicShareView 了,onMeasure、layout 的步骤也完成了,但图片还未从网络、磁盘加载完,ImageView 里的 Bitmap 是不存在的 。这时,我们再这么做:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void share(View target) {

	// 测量 DynamicShareView 的大小
   measureManually(target, ScreenUtil.getScreenWidth(mContext),
       ScreenUtil.getScreenWidth(mContext) + DensityUtil.dip2px(mContext, 175.0F));
// 布局 DynamicShareView
   target.layout(0, 0, target.getMeasuredWidth(), target.getMeasuredHeight());
   //  生成 Bitmap
   Bitmap bitmap = generateBitmap(target, target.getMeasuredWidth(), target.getMeasuredHeight());

   //  将 Bitmap 保存为临时文件
   try {
     saveBitmap(bitmap);
   } catch (FileNotFoundException e) {
     e.printStackTrace();
   } catch (IOException e) {
     e.printStackTrace();
   }

   mContext.startActivity(createShareIntent());
 }

生成的 Bitmap 很可能是没有图片的。因为这些图片都是需要 Glide远程图片服务器加载,解析后才能得到的。而我们并不知道加载这些图片需要多久,甚至都没有等待这些加载工作完成,就直接填充数据到 DynamicShareView 上,然后满怀期待地生成 Bitmap 了…

解决方案

简单来说,就是在知道图片全部加载完成之后,我们再生成最后的分享图,关键就是等!

这里我使用了一个计数器,每当 Glide 加载完成,拿到 Bitmap 后,我就让计数器+1,当计数器等于需要加载的图片个数时,回调。如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Glide.with(viewGroup.getContext())
          .load(mImages.get(position).getImageUrl())
          .asBitmap()
          .into(new SimpleTarget<Bitmap>() {
            @Override public void onResourceReady(Bitmap resource,
                GlideAnimation<? super Bitmap> glideAnimation) {

              ivCover.setImageBitmap(resource);
              mloadImageCount++;
              if (mloadImageCount == getCoverCount() + 1) {
                mOnImageLoadedListener.onImageLoaded();
              }
            }
          });

外部构造 Adapter 实例时,传入一个 OnImageLoadedListener 即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// MyAdapter 的签名
public MyAdapter(@NonNull Context context, List<Image> images, @NonNull Bottom bottom,
      @NonNull OnImageLoadedListener onImageLoadedListener) {
      
 }

优化&思考

  • Bitmap 的生成其实是一个耗时的操作,能否搬移到工作线程中
  • 有没有比计数器更好的实现来解决异步问题
  • 该实现有没有 leak memory 的风险
  • 如果下半部分的文字内容很长,是否会导致 OOM
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-12-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
终于有人把数据指标体系的概念和价值讲明白了
导读:数据指标体系是构建数据中台的重要一环。数据指标的建立让运营及产品人员更直观地看到基本指标的变动,让数据分析师更便捷地开展数据分析工作。
IT阅读排行榜
2021/07/12
6.9K1
数据仓库&数据指标&数据治理体系搭建方法论
英文名称为Data Warehouse,可简写为DW或DWH。数据仓库的目的是构建面向分析的集成化数据环境,为企业提供决策支持(Decision Support)。它出于分析性报告和决策支持目的而创建。
王知无-import_bigdata
2021/06/01
5.8K0
数据仓库&数据指标&数据治理体系搭建方法论
7000字详解数据指标体系如何从设计到落地
导语:几乎所有的数据分析工作都会提到一个词——“建立数据指标体系”,虽然这个词对于大家来说并不陌生,但是数据指标到底是什么以及如何具体的搭建,很多人还是一头雾水的。
肉眼品世界
2021/11/11
6K0
7000字详解数据指标体系如何从设计到落地
大数据实践:数据指标中心的建设思路
做好业务分析的重点在于数据分析师要有良好的专业素养:一方面要有过硬的专业技能、了解业务;另一方面要能够通过合作和协调,让分析策略可以落地并正向影响业务。这篇文章将从数据认知开始,给大家讲讲数据分析和指标体系建设。
大数据真好玩
2021/11/16
1.4K0
大数据实践:数据指标中心的建设思路
数据仓库指标体系搭建实战
业务板块定义原则:业务逻辑层面进行抽象、物理组织架构层面进行细分,可根据实际业务情况进行层级分拆细化,层级分级建议进行最多进行三级分拆,一级细分可公司层面统一规范确定,二级及后续拆分可根据业务线实际业务进行拆分。
五分钟学大数据
2022/10/05
1.8K0
数据仓库指标体系搭建实战
中国指标中台市场研究报告:指标体系建设方法论及指标中台建设看这一篇就够了
18年的时候开始关注Kyligence,因为当时想做UV指标的多维度灵活查询,当时选的技术方案是基于Kylin的Cube预计算。最近看到Kyligence发布了《中国指标中台市场研究报告》,感觉关于指标化管理从方法论到指标管理平台产品的建设思路覆盖的还是非常全面的,分享给大家。
数据干饭人
2023/03/03
1.2K0
中国指标中台市场研究报告:指标体系建设方法论及指标中台建设看这一篇就够了
数据指标是什么?必知必会的数据指标类型都在这了
导读:数据指标体系是构建数据中台的重要一环。数据指标的建立让运营及产品人员更直观地看到基本指标的变动,让数据分析师更便捷地开展数据分析工作。
数据万花筒
2021/07/05
7.3K0
数据指标是什么?必知必会的数据指标类型都在这了
超硬核 | 一文带你入门用户画像
之前开发过一个画像项目,并为大家介绍了项目过程中部分开发的细节,例如PSM,RFE,USG等模型的标签开发落地。但是后来考虑到对于没有画像开发经验,尤其是零基础的大数据小白而言不是很友好,理解起来也不是很容易。正好最近在看一些文献资料,所以,我又专门开了一个专题,打算重新为大家讲解关于用户画像的知识。感兴趣的小伙伴记得关注加星标,每天第一时间收获技术干货!
大数据梦想家
2021/01/27
1.9K0
超硬核 | 一文带你入门用户画像
想要精准营销,从学习搭建一套对的标签体系开始丨 DTVision 分析洞察篇
在人与人打交道的过程中,我们会在有意无意间给周围的人通过贴标签的方式进行大致的判断,比如好说话的、难相处的、聪明的、爱热闹的…… 贴标签就是用最快的速度将人和事归类,这是人类运用 “模式识别” 认识世界、进行社会交往最便捷的方式之一。
袋鼠云数栈
2022/08/08
1K0
以指标为中心的ABI平台,重塑企业数字化经营|爱分析报告
在市场竞争日益激烈的当下,数字化经营对于企业的重要性不言而喻。在提高竞争力、降低成本、创新业务模式以及提升客户体验等方面,指标的应用能力一定程度上决定了企业数字化经营效益的高低。
用户10505706
2023/08/25
5120
以指标为中心的ABI平台,重塑企业数字化经营|爱分析报告
数据赋能:产品数据化运营四步法
产品经理做了很多从0-1的事情,而产品运营则是在这个1后面不断地加0,从1变成10、100、十亿。相信每个产品经理都希望自己的产品可以从1到无穷大,本文主要从数据运营的角度,分享数据在产品运营过程能够起到的作用,数据如何赋能运营。
数据干饭人
2022/07/01
5610
数据赋能:产品数据化运营四步法
实用五步法教会你指标体系的设计与加工
今天我们来和大家聊一聊一个新话题,一个对于企业业务发展十分关键的东西 —— 指标。
袋鼠云数栈
2022/09/28
1.7K0
数据中台实战(05)-如何统一管理纷繁杂乱的数据指标?
元数据在指标管理、模型设计、数据质量和成本治理四个领域都发挥作用,这些领域构成数据中台OneData 数据体系。今天逐一了解元数据在上述领域的应用
JavaEdge
2023/10/07
1.5K0
数据中台实战(05)-如何统一管理纷繁杂乱的数据指标?
深入了解:标签体系——企业运营中不可或缺的一环
而运营的精准化需要海量数据来支撑,重中之重是建设一个适合自身的 CDP,并且用好它。
努力的阿飞
2024/01/19
2860
数仓中指标-标签,维度-度量,自然键-代理键等常见的概念术语解析
作为一个数据人,是不是经常被各种名词围绕,是不是对其中很多概念认知模糊。有些词虽然只有一字之差,但是它们意思完全不同,今天我们就来了解下数仓建设及数据分析时常见的一些概念含义及它们之间的关系。
五分钟学大数据
2021/09/22
2.2K0
干货 | 用数据描述和驱动业务,携程指标标准化管理实践
Chao,携程资深数据分析经理,关注数据治理、数据仓库和数据分析领域。致力于数据使用效率及价值提升。
携程技术
2021/06/24
6480
干货 | 用数据描述和驱动业务,携程指标标准化管理实践
企业级指标体系搭建全流程落地
但是,在实际情况中,单一一个指标很难描述清楚业务现状,例如指标为“支付订单数”,那么“支付订单数”的增加究竟是拉新带来的呢?还是运营促活带来的呢?这样就可能出现不同部门之间相互扯皮的现象。
活用数据
2022/05/13
9070
企业级指标体系搭建全流程落地
如何快速搭建常用的数据指标体系
大家好,好久不见!前段时间工作太忙,所以暂停了一段时间更新公众号。感谢大家一直以来的支持与陪伴,接下来我会继续不定期更新干货内容来回馈大家!本期内容我想重点给大家讲一讲在数据分析工作中非常常见的模块——如何快速搭建数据指标体系,希望对大家有所帮助!
用户7569543
2023/01/11
5770
【思考】数据资产管理痛点以及解决思路
元数据作为记录数据的数据,随着公司数据资产的增加,需要对其进行有效的管理,从而能够快速获取到数据的相关信息并进行使用。
857技术社区
2022/05/17
1.5K0
【思考】数据资产管理痛点以及解决思路
【金猿案例展】杭州联合银行——大数据系列平台建设
本项目案例由网易数帆投递并参与“数据猿年度金猿策划活动——《2022大数据产业年度创新服务企业》榜单/奖项”评选。
数据猿
2023/01/30
5310
推荐阅读
相关推荐
终于有人把数据指标体系的概念和价值讲明白了
更多 >
LV.0
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档