项目中经常会用到分享的功能,有分享链接也有分享图片,其中分享图片有的需要移动端对屏幕内容进行截取分享,说白了就是将view 转成bitmap 再到图片分享,还有一种情况是将不可见的view 转成bitmap ,这种view是没有直接显示在界面上的,需要我们使用inflate 进行创建的view。
第一种 可见view
先看通过 DrawingCache 方法来截取普通的view,获取它的视图(Bitmap)。
private Bitmap createBitmap(View view) { view.buildDrawingCache(); Bitmap bitmap = view.getDrawingCache(); return bitmap; }这个方法适用于view 已经显示在界面上了,可以获得view 的宽高实际大小,进而通过DrawingCache 保存为bitmap。
第二种
如果要截取的view 没有在屏幕上显示完全的,例如要截取的是超过一屏的 scrollview ,通过上面这个方法是获取不到bitmap的,需要使用下面方法,传的view 是scrollview 的子view(LinearLayout)等, 当然完全显示的view(第一种情况的view) 也可以使用这个方法截取。
public Bitmap createBitmap2(View v) { Bitmap bmp = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888); Canvas c = new Canvas(bmp); c.drawColor(Color.WHITE); v.draw(c); return bmp; }第三种 不可见view或者是xml
这种是view完全没有显示在界面上,通过inflate 转化的view,这时候通过 DrawingCache 是获取不到bitmap 的,也拿不到view 的宽高,以上两种方法都是不可行的。第三种方法通过measure、layout 去获得view 的实际尺寸。如果出现图片只截取了上面的一部分 那么你就需要计算控件自适应的高度了
//如果出现图片只截取了上面的一部分 那么你就需要计算控件自适应的高度了 public static Bitmap createBitmap3(ViewGroup v, int width, int height) { int h = 0; //测量使得view指定大小 int measuredWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); int measuredHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); v.measure(measuredWidth, measuredHeight); //调用layout方法布局后,可以得到view的尺寸大小 v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight()); //获取当前控件的所以子控件的高 for (int i =0; i < v.getChildCount(); i++) { h += v.getChildAt(i).getHeight(); } Bitmap bmp = Bitmap.createBitmap(v.getWidth(), h, Bitmap.Config.RGB_565); Canvas c = new Canvas(bmp); c.drawColor(Color.WHITE); v.draw(c); return bmp; }计算View的高度 举例测量TextVIew的高度 先获取外围的宽度 红色,蓝色框然后转像素density ,然后获取推荐的宽度 ,在获取屏幕宽度 ,用屏幕宽度-红色和蓝色的density-推荐的宽度=剩余aaa的文字所能占得宽度 ,现在已经得到宽度了 剩下的就是获取TextView所赋值的总宽度然后除以剩余所占的宽度就可以得到行数了,然后获取TextView的单行高度*行数就可以得到TextView自适应的高度了 这样就解决了图片只截取一部分的问题了 ,下面的方法最后返回了一个int类型的数据,把返回的int数据加上屏幕的高度在传给View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);这里面的height就行了,因为我们刚才的计算都只是在计算超出没有显示的那一部分的高度所以加上屏幕宽度就是我们整个的图片所需要的绘制的高度,具体可以参考下面的MainActivity里面调用的
//dp转像素 public static int getdpPixels(int dp, Activity activity,View oneview,TextView twoview){//内部文字的宽度 int textWidth=0; WindowManager wm = (WindowManager) activity.getSystemService(activity.WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(dm); //外边的dp转像素 int Pixels =(int)dm.density * dp; //获取屏幕宽度 int widthPixels = dm.widthPixels; if(oneview!=null){ //获取推荐理由的文字宽度 TextView liyou = oneview.findViewById(R.id.liyou); textWidth = (int) android.text.Layout.getDesiredWidth(liyou.getText(), liyou.getPaint()); } //剩余像素 int i1 = widthPixels - Pixels - textWidth; //获取本文本文字的总长度 int textW = (int) android.text.Layout.getDesiredWidth(twoview.getText(), twoview.getPaint()); //判断行数 int linenumber=0; if(textW/i1<1){ linenumber=1; }else { linenumber=(textW/i1)+1; } Rect bounds = new Rect(); TextPaint mTextPaint = twoview.getPaint(); String mText = twoview.getText().toString(); mTextPaint.getTextBounds(mText, 0, mText.length(), bounds); //文字单行高度 int oneheight = bounds.height(); //将高度统计然后赋值给画板 return oneheight*linenumber; }
然后在Main里面调用就行 如下 这里我要重点说明一下如果想要加载图片必须要用同步不然你用异步请求的话那就必须请求完成后再把布局放进去,如果不想使用同步那么你也可以用handler来判断异步线程是否执行完成,执行完成在将布局放进画布
public class MainActivity extends AppCompatActivity { private int height=30;//这个是整个布局距离顶部和底部的距离 你也可以适量设置 @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.aaa); ImageView image = findViewById(R.id.image); View view = LayoutInflater.from(this).inflate(R.layout.activity_main, null, false); RelativeLayout viewById1 = view.findViewById(R.id.share_relat);//需要生成图片的布局 TextView content = viewById1.findViewById(R.id.content);//aaaa的textview 需要测量的 TextView tuijian = oneview.findViewById(R.id.liyou);//推荐 //获取屏幕宽和高 Point outSize = new Point(); getWindowManager().getDefaultDisplay().getRealSize(outSize); int widthPixels = outSize.x; int heightPixels = outSize.y; //获取到TexTview的的高度 也就是超出没有显示的高度,这个80是外围两层布局距离左右两边的距离我直接就算出dp传进去,viewById1这个是我上面的推荐的textview的view,content是需要测量的view也就是aaaa的那个textview height += getdpPixels(80,this,tuijian ,content); //这里传值屏幕宽高,得到的视图即全屏大小 viewById1是需要生成图片的布局 image.setImageBitmap(BitmapViewUtils.createBitmap3(viewById1, widthPixels, heightPixels+height)); } }
另外写了个简易的保存图片的方法,方便查看效果的。
private void saveBitmap(Bitmap bitmap) { FileOutputStream fos; try { File root = Environment.getExternalStorageDirectory(); File file = new File(root, "test.png"); fos = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }-----------------------------------
标题写到这就结束了
下面介绍一些获取屏幕宽和高的方法
不过里面的方法已经过时了。
总结:
方法2和方法3查看源码可知其实是一样的逻辑。
public void getSize(Point outSize) { synchronized (this) { updateDisplayInfoLocked(); mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments()); outSize.x = mTempMetrics.widthPixels; outSize.y = mTempMetrics.heightPixels; } } public void getMetrics(DisplayMetrics outMetrics) { synchronized (this) { updateDisplayInfoLocked(); mDisplayInfo.getAppMetrics(outMetrics, getDisplayAdjustments()); } }我们注意到方法1,2,3显示屏幕的分辨率是 1440x2768,而方法4,5显示的屏幕的分辨率是1440x2960。为什么是这样了?
答:显示区域以两种不同的方式描述。包括应用程序的显示区域和实际显示区域。应用程序显示区域指定可能包含应用程序窗口的显示部分,不包括系统装饰。 应用程序显示区域可以小于实际显示区域,因为系统减去诸如状态栏之类的装饰元素所需的空间。 使用以下方法查询应用程序显示区域:getSize(Point),getRectSize(Rect)和getMetrics(DisplayMetrics)。实际显示区域指定包含系统装饰的内容的显示部分。 即便如此,如果窗口管理器使用(adb shell wm size)模拟较小的显示器,则实际显示区域可能小于显示器的物理尺寸。 使用以下方法查询实际显示区域:getRealSize(Point),getRealMetrics(DisplayMetrics)。