Android 开发过程中截图分享功能实现

效果图

无图言*,上图!由于是 Demo 哈,所以就没有怎么美化,将就看啦~

图片描述 图片描述

前言

虽然咱们的标题叫《Android 开发过程中截图分享功能实现》,但是咱们本篇文章主要是讲截图功能的实现,分享功能的话其实没有必要讲的,因为各位同学需要分享到的平台可能不大相同,但是都有一个共同点,那就是分享的是图片文件或者图片文件的路径。


所以,我为什么要写这篇文章呢?因为我最近的项目里有一个需求,就是分享卡片图片。这个时候有的同学可能就坐不住了,分享卡片图片?我让做 UI 的同学切一个放 app 里不就好了吗?


是的,你大可以这么做。但是,各位同学注意了啊~ 你要知道,这个图片也是个文件,是要占内存的,也就是说如果要放到 app 里的话,那么势必会增大 apk 的体积。而且,咱们的这个卡片的布局排版一般都是固定的,变化的是内容,你总不能说让做 UI 的同学把每一种情况下的卡片都切一份吧,这肯定是不现实的。因为有很多情况下的内容数据都是从网上获取的,比如淘宝的商品分享卡片功能。


图片描述

那么,有没有什么好办法呢?有!当然有啦~


实现思路

  • 编写布局,排版
  • 动态设置内容
  • 截图
  • 将截图保存为 file 文件
  • 分享 file 文件或 file 路径

引入依赖(可选)

1
2
// 使用 view.drawToBitmap() 扩展函数和 view.isInvisible 扩展属性时需要
implementation 'androidx.core:core-ktx:1.7.0'

编写代码

这里我们一共有两个 xml 布局,一个是用来承载 触发截图按钮和显示截图效果的(screenshot_activity.xml),另一个是用来方便 布局分享卡片 的(screenshot_card_layout.xml)。


xml 布局

screenshot_activity.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#03A9F4"
android:orientation="vertical"
android:padding="20dp"
tools:context=".ScreenshotActivity">

<Button
android:id="@+id/btn_create_screenshot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="生成截图" />

<ImageView
android:id="@+id/iv_screenshot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="20dp"
android:contentDescription="生成后的截图展示,实际开发时将会直接保存为 file 文件,以便分享发送" />

</LinearLayout>

screenshot_card_layout.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:background="#fff"
android:orientation="vertical"
android:padding="10dp">

<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">

<ImageView
android:id="@+id/iv_cover"
android:layout_width="160dp"
android:layout_height="160dp"
android:layout_gravity="center"
android:contentDescription="动态设置的商品封面"
android:src="@drawable/ic_launcher_background" />

<ImageView
android:id="@+id/iv_qr_code"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="end|bottom"
android:layout_margin="10dp"
android:background="#2196F3"
android:contentDescription="动态设置的二维码" />

</FrameLayout>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginVertical="10dp"
android:contentDescription="假装我是分享语"
android:ellipsize="end"
android:maxLines="1"
android:paddingVertical="10dp"
android:text="我发现了一款肥肠不错的产品,你也来试试吧!" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">

<TextView
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:contentDescription="假装我是标题"
android:elevation="@dimen/cardview_compat_inset_shadow"
android:maxLines="2"
android:padding="6dp"
android:text="Apple/苹果 14 英寸 MacBook Pro Apple M1 Pro 8 核中央处理器 14核图形处理器 16GB" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="假装我是价格"
android:padding="10dp"
android:text="¥1.59万"
android:textColor="#FB4828"
android:textSize="16sp" />

</LinearLayout>

</LinearLayout>

kotlin 代码

ScreenshotActivity.kt

Tips:别忘了在 AndroidManifest.xml 清单文件中注册哦~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import android.content.res.Resources
import android.graphics.Bitmap
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.applyCanvas
import androidx.core.view.ViewCompat

class ScreenshotActivity : AppCompatActivity() {

private val mBtnCrateScreenshot: Button by lazy { findViewById(R.id.btn_create_screenshot) }
private val mIvScreenshot: ImageView by lazy { findViewById(R.id.iv_screenshot) }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.screenshot_activity)
initEvent()
}

private fun initEvent() {
mBtnCrateScreenshot.setOnClickListener {
createQrCodeBitmap()
}
}

private fun createQrCodeBitmap() {
// 加载布局
val layout = LayoutInflater.from(this).inflate(R.layout.screenshot_card_layout, null)
// 将该布局隐藏,主要是防止用户看到,同时又不会影响布局的测量
layout.isInvisible = true
// 实际上,只要能添加到 View 视图树上即可保证后面的 post 方法执行
val decorView = window.decorView as ViewGroup
// 这里需要指定宽高,建议和布局中的一致,否则可能会留白太多
decorView.addView(layout, ViewGroup.LayoutParams(200.dp, ViewGroup.LayoutParams.WRAP_CONTENT))
// 需要等 view 布局完成后再执行截图操作(因为我们指定了宽高,所以不需要考虑 view 的测量问题)
layout.post {
// config 参数为生成的 bitmap 质量,一般来说 Bitmap.Config.RGB_565 就够了,根据实际开发需求进行调整
// drawToBitmap 为 androidx.core.view 包下的官方扩展函数
val bitmap = layout.drawToBitmap(config = Bitmap.Config.RGB_565)
// 设置 bitmap 到 ImageView 展示效果,真实开发时直接保存为 file 文件,然后分享
save2FileAndShare(bitmap)
// 移除刚刚添加的 View(你也可以不移除,以便进行复用,根据实际开发需求进行调整)
decorView.removeView(layout)
}
}

/**
* 保存文件并分享
*/
private fun save2FileAndShare(bitmap: Bitmap) {
// 这里通过设置 bitmap 到 ImageView 上替代保存文件和分享的步骤,也可以看到实际的截图效果
mIvScreenshot.setImageBitmap(bitmap)
}

/**
* Copy from androidx.core:core-ktx:1.7.0
*
* Return a [Bitmap] representation of this [View].
*
* The resulting bitmap will be the same width and height as this view's current layout
* dimensions. This does not take into account any transformations such as scale or translation.
*
* Note, this will use the software rendering pipeline to draw the view to the bitmap. This may
* result with different drawing to what is rendered on a hardware accelerated canvas (such as
* the device screen).
*
* If this view has not been laid out this method will throw a [IllegalStateException].
*
* @param config Bitmap config of the desired bitmap. Defaults to [Bitmap.Config.ARGB_8888].
*/
private fun View.drawToBitmap(config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap {
if (!ViewCompat.isLaidOut(this)) {
throw IllegalStateException("View needs to be laid out before calling drawToBitmap()")
}
return Bitmap.createBitmap(width, height, config).applyCanvas {
translate(-scrollX.toFloat(), -scrollY.toFloat())
draw(this)
}
}

/**
*
* Copy from androidx.core:core-ktx:1.7.0
*
* Returns true when this view's visibility is [View.INVISIBLE], false otherwise.
*
* ```
* if (view.isInvisible) {
* // Behavior...
* }
* ```
*
* Setting this property to true sets the visibility to [View.INVISIBLE], false to [View.VISIBLE].
*
* ```
* view.isInvisible = true
* ```
*/
private inline var View.isInvisible: Boolean
get() = visibility == View.INVISIBLE
set(value) {
visibility = if (value) View.INVISIBLE else View.VISIBLE
}


/**
* Int 对象的扩展函数,方便转换为 dp
*/
private val Int.dp
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, toFloat(), Resources.getSystem().displayMetrics)
.toInt()
}


总结

代码也都很简单,不过第一次做这个功能的同学可能会遇到几个坑点。

  • 加载的布局没有添加到 View 视图树上,导致未布局视图,不会执行 view.post 方法
  • 未指定 View 的宽高,导致留白过多,不符合预期效果

如果对你有帮助的话,欢迎一键三连+关注哦~


Android 开发过程中截图分享功能实现
https://blog.funkt.cn/2022/08/16/1559556991213375490/
作者
anjiemo
发布于
2022年8月16日
许可协议
未经作者授权,禁止转载