获取View的Bitmap几种方法:
一、创建一个新的空Bitmap,然后再根据它来创建一个Canvas,最后调用View的draw方法将View画到Canvas上
不建议采用该方式
public Bitmap createViewBitmap(View v) {
Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
v.layout(0, 0, v.getWidth(), v.getHeight());
v.draw(canvas);
return bitmap;
若view没有显示出来,则要去获取宽高,更完整的参考:https://blog.csdn.net/dongbaoming/article/details/54172801
性能优化:
1 若对生成的图片没有透明度分量的要求,则可以将生成图片改为RGB565 而非ARGB8888
二、View的DrawingCache 方法
6.0以及以下版本建议方式 耗时大概0.012s这样 因为取的是view的缓存会快一些
若想获取可见view的bitmap 则view设置为 getActivity().getWindow().getDecorView()获取decorView即可
这种方式获取不到surfaceView,因为SurfaceView的绘制其实是我们自己完成的,分为Surface和View 而通过getDrawingcache的方式获取的是view的内容
这种方案的原理是 获取该view在内存中的缓存
/**
* 获取 View 的缓存视图
*
* @param view
* @return
*/
private fun getCacheBitmapFromView(view: View): Bitmap? {
// 开启view的bitmap cache
view.isDrawingCacheEnabled = true
// 获取view的bitmap cache
val drawingCache = view.drawingCache // getDrawingCache()
val bitmap: Bitmap?
if (drawingCache != null) {
//创建一个DrawingCache的拷贝,因为DrawingCache得到的位图在禁用后会被回收
bitmap = Bitmap.createBitmap(drawingCache)
// 将cache关闭 节省内存
view.isDrawingCacheEnabled = false
view.destroyDrawingCache()
} else {
bitmap = null
}
return bitmap
}
默认 getDrawingCache 得到的bitmap 为 ARGB8888
问题: getDrawingCache 返回的为空
原因: 1 View没有显示到界面上,获取的 VIew的宽高为空
三、 PixelCopy.request
android7.0以上推荐该方式 耗时大概0.023s这样 因为是像素级拷贝 会慢一些
该方式主要通过像素级的拷贝,因此可以获取任意view的bitmap
1 获取surfaceView的bitmap android7.0以上
final Bitmap bitmap = Bitmap.createBitmap(surfaceView.getWidth(), surfaceView.getHeight(), Bitmap.Config.ARGB_8888);
final HandlerThread handlerThread = new HandlerThread(GetLastFrameBitmapUtil.class.getSimpleName());
handlerThread.start();
PixelCopy.request(surfaceView, bitmap, new PixelCopy.OnPixelCopyFinishedListener() {
@Override
public void onPixelCopyFinished(int copyResult) {
try {
if (copyResult == PixelCopy.SUCCESS && bitmap != null) {
Logger.t(TAG).d("interactKP: get >6.0 SurfaceView bitmap success.");
} else {
Logger.t(TAG).e("interactKP: get >6.0 SurfaceView bitmap failed.");
}
handlerThread.quitSafely();
} catch (Throwable t) {
Logger.t(TAG).e("interactKP: get >6.0 SurfaceView throwable = " + t);
}
}
}, new Handler(handlerThread.getLooper()));
2 获取普通view的内容 android7.0以上
private var mBitmap: Bitmap? = null
private var mHandlerThread = HandlerThread(TestActivity::class.java.simpleName)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 获取layout的位置
val location = IntArray(2)
rl_rl.getLocationInWindow(location)
PixelCopy.request(window,
Rect(location[0], location[1], location[0] + rl_rl.width, location[1] + rl_rl.height),
mBitmap!!, { copyResult ->
try {
if (copyResult == PixelCopy.SUCCESS) {
Log.e("TestActivity", "interactKP: get >6.0 bitmap success.")
} else {
Log.e("TestActivity", "interactKP: get >6.0 bitmap failed.")
}
} catch (t: Throwable) {
Log.e("TestActivity", "interactKP: get >6.0 throwable = $t")
}
}, Handler(mHandlerThread.looper))
}
四、6.0以下获取surfaceView的bitmap
(1) 在SurfaceView中实时输出bitmap 但是要注意好内存资源的占用和回收
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
yourSurfaceView.draw(c);
(2)root的手机 通过framebuffer获取
(3)改造成TextureView
通过
val bitmap = Bitmap.createBitmap(it.width, it.height, Bitmap.Config.ARGB_8888)
textureView.getBitmap(bitmap)
// 获取直接获取FrameBuffer的glReadPixels()?
即可获取
(4) GLSurfaceView
https://blog.csdn.net/afei__/article/details/51614375
https://stackoverflow.com/questions/27817577/android-take-screenshot-of-surface-view-shows-black-screen/27824250#27824250
(5)通过adb shell方式获取
截取带导航栏的整个屏幕 可以截取手机任何界面任何view
adb shell screencap 路径.png
要用png保存图片 jpg的打不开
该命令读取系统的framebuffer,需要获得系统权限:
(1). 在AndroidManifest.xml文件中添加
<uses-permissionandroid:name="android.permission.READ_FRAME_BUFFER"/>
(2)得使用系统权限 shardUserId="android.uid.system"
(6)截取非含当前应用的屏幕部分
先开启录屏 通过 MediaProjectionManager 然后将画面保存
framebuffer是linux为显示设备提供的接口 是一个显示缓冲区 可以进行读写操作
android中的framebuffer数据是存放在 /dev/graphics/fb0 文件中的
alibaba viewPager库:https://github.com/alibaba/UltraViewPager
一、build.gradle
//gradle
implementation('com.alibaba.android:ultraviewpager:1.0.7.7@aar') {
transitive = true
}
二、加载UltraPager和Adapter
val ultraViewPager = findViewById<View>(R.id.ultra_viewpager) as UltraViewPager
// 有水平和垂直方向
ultraViewPager.setScrollMode(UltraViewPager.ScrollMode.HORIZONTAL)
// 设置多屏显示 1.0f为单屏 如0.5则是左右两边都留图片的一半显示
ultraViewPager.setMultiScreen(1.0f)
// 设置 转换的动态效果,有两种
ultraViewPager.setPageTransformer(true, UltraDepthScaleTransformer())
//设定页面循环播放
ultraViewPager.setInfiniteLoop(false)
//设定页面自动切换 间隔2秒
//mUltraViewPager.setAutoScroll(2000)
// UltraViewPager的高度会自动调整到child view的高度
//ultraViewPager.setAutoMeasureHeight(true)
// 设置后会以此宽高比调整child view的高度
//ultraViewPager.setItemRatio(1.0)
// 以设定的宽高比来绘制UltraViewPager
//ultraViewPager.setRatio(1.5f)
/** initialize UltraPagerAdapter,and add child view to UltraViewPager */
val adapter = UltraPagerAdapter(this)
ultraViewPager.adapter = adapter
三、UltraPagerAdapter类:
这里主要 实现了
加载3个自定义的layout
设置布局的点击监听
更改某个TextView的字体
获取View的缓存视图
class UltraPagerAdapter(private val context: Context) : PagerAdapter(), View.OnClickListener {
private val TAG = "UltraPagerAdapter"
companion object {
@JvmField
var viewCacheBitmap :Bitmap ?=null
}
override fun getCount(): Int {
return 3
}
override fun isViewFromObject(view: View, obj: Any): Boolean {
return view === obj
}
/** 加载3个自定义的layout */
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val layoutRelativeLayout01 = LayoutInflater.from(container.context).inflate(R.layout.template01, null)
val layoutRelativeLayout02 = LayoutInflater.from(container.context).inflate(R.layout.template02, null)
val layoutRelativeLayout03 = LayoutInflater.from(container.context).inflate(R.layout.template03, null)
val relativeLayout01 = layoutRelativeLayout01.findViewById(R.id.template_01) as RelativeLayout
val relativeLayout02 = layoutRelativeLayout02.findViewById(R.id.template_02) as RelativeLayout
val relativeLayout03 = layoutRelativeLayout03.findViewById(R.id.template_03) as RelativeLayout
val tvDay01 = layoutRelativeLayout01.findViewById(R.id.tv_template01_day) as TextView
val tvDay02 = layoutRelativeLayout02.findViewById(R.id.tv_template02_day) as TextView
val tvDay03 = layoutRelativeLayout03.findViewById(R.id.tv_template03_day) as TextView
/** 设置字体样式 */
val textFont = Typeface.createFromAsset(context.assets, "fonts/CharlemagneStd-Bold.otf")
if (textFont != null) {
tvDay01.typeface = textFont
tvDay02.typeface = textFont
tvDay03.typeface = textFont
} else {
Logger.t(TAG).e("字体库不存在")
}
// 设置监听
relativeLayout01.setOnClickListener(this)
relativeLayout02.setOnClickListener(this)
relativeLayout03.setOnClickListener(this)
/** 加载界面 */
when (position) {
0 -> {
container.addView(relativeLayout01)
return relativeLayout01
}
1 -> {
container.addView(relativeLayout02)
return relativeLayout02
}
2 -> {
container.addView(relativeLayout03)
return relativeLayout03
}
}
return relativeLayout01
}
override fun destroyItem(container: ViewGroup, position: Int, obj: Any) {
val view = obj as RelativeLayout
container.removeView(view)
}
override fun onClick(view: View?) {
if (view != null) {
viewCacheBitmap = getCacheBitmapFromView(view)
Logger.t(TAG).e("viewCacheBitmap width" + viewCacheBitmap?.width
+ " viewCacheBitmap height" + viewCacheBitmap?.height)
}else{
Logger.t(TAG).e("View is null")
}
val sharedPicDialog = SharedPicDialog(context)
sharedPicDialog.show()
}
/**
* 获取 View 的缓存视图
*
* @param view
* @return
*/
private fun getCacheBitmapFromView(view: View): Bitmap? {
val drawingCacheEnabled = true
view.isDrawingCacheEnabled = drawingCacheEnabled
view.buildDrawingCache(drawingCacheEnabled)
val drawingCache = view.drawingCache
val bitmap: Bitmap?
if (drawingCache != null) {
bitmap = Bitmap.createBitmap(drawingCache)
view.isDrawingCacheEnabled = false
} else {
bitmap = null
}
return bitmap
}
}
问题1 系统viewpager滑动到第二页,再屏幕旋转,然后关闭viewpager(View.GONE)再屏幕旋转回去,再打开ViewPager(View.VISIBLE) 此时viewPager的内容位置错位
viewpager 显示的时候 会去屏幕旋转一下,重新layout,但是layout计算的值错误
解决: 使用INVISIBLE代替GONE