追求截图的极致-牛牛截图再添新成员-截长图功能

Posted 大蓝头

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了追求截图的极致-牛牛截图再添新成员-截长图功能相关的知识,希望对你有一定的参考价值。

很久没有写关于截图控件的文章了,借着这次截图控件新功能升级,向大家介绍一下牛牛截图!

大家知道,我们对于截图控件的功能性、易用性以及体验都有着非常高的要求,总结如下:

  • 能力方面 我们支持多显示器、高清屏DPI放大支持、窗口区域的自动识别、多语言支持等等众多截图控件所不具备的能力
  • 涂鸦功能方面 我们支持矩形、圆形、画刷、荧光笔、马赛克、文字等等功能,同时具备涂鸦二次编辑、文字及图片水印、工具栏自定义扩展功能等等非常实用的高价值能力
  • 集成能力方面 我们支持集成进任意一款浏览器,支持集成进任何一个桌面软件中
  • 平台兼容性方面 我们支持Windows、macOS以及Linux等所有主流操作系统,同时兼容不同架构下国产操作系统,真正做到全面兼容

今天,我们截图控件更进一步,新增加了一项大家都喜欢的功能-截长图,希望大家能够喜欢!

原理

截长图,顾名思义,就是能够选中有限的区域,然后利用鼠标滚动后,将原本未显示的区域也一并截取下来,并最终合并成一张完整的长图;它的应用范围也非常的广泛,比如信息分享、证据固定、数据存档等等。

从前期的技术调研、测试,到后来的功能开发与集成,最后进行细节完善、兼容性优化等处理,我们一共花了半个多月的时间;回过头来看,原理和流程大致如下:

  • 利用现有截图中的截图区域选择功能,框选要截长图的区域

  • 点击工具栏上指定按钮,触发截长图功能,定时的截取选中区域的图片,同时触发滚动条向下滚动 (实际应用中也可以不触发,而是人为的进行滚动)

  • 每次截取下来的图片,在内存中利用opencv进行灰度化,然后对相邻的两张图片进行模式匹配,标记出来重叠的区域,在去掉重叠区域的前提下将两张图片进行合并

    因为图片的宽度都是一样的,所以只需要考虑高度上的重叠因素

  • 停止截长图,将最终合成的图片复制到剪贴板,与原有截图流程进行对接

原理上来讲,不是太难,主要在于性能、内存、兼容性以及使用体验上的种种优化。

使用方法

目前我们已经上线了Windows的桌面版控件以及浏览器版本控件,大家如果喜欢,可以到我们的网站上进行体验,大致流程如下:

  • 打开https://leeqia.com/screencapture/onlinedemo/
  • 点击更多个性化选项,选中显示预截图窗口(因为是浏览器页面上测试体验,要截取的窗口可能被浏览器遮挡,所以应用此功能),点击屏幕截图按钮,根据提示安装新版本控件
  • 截图功能呼出后,选择指定的窗口区域,注意不要选中不会滚动的区域(此处我们以我们的网站页面为例)
  • 点击工具栏上的截长图按钮,此时目标窗口区域开始滚动,截长图功能正在进行中
  • 点击完成,停止截长图,截好的图将会回送到浏览器页面中进行显示

完整的操作流程如下:

整个截长图流程保存下来的图片如下:

桌面版控件测试可以下载如下程序包:

http://leeqia.com/download/NiuniuCapture.zip

后记

目前我们只是在Windows平台上实现了截长图的功能,Linux以及macOS平台的截长图功能正在开发中,敬请期待!

牛牛截图从2014年上线第一版以来,已经过去了9年了,未来的路还很长,我们将继续砥砺前行!新功能上线不是结束,我们会持续进行迭代优化,努力打造全网最优秀的截图体验!

Android实现截屏和截长图功能的各种方法

  微信好友或者朋友圈的分享,可以是普通的截图分享,也可以是截取长图的分享,甚至还会有需求让你拼上生成的二维码和logo图片,下面我们直接来看看这些方法的使用:


先说一下拼接三张不同的图片后有黑色背景的解决方案(在下面的6拼接合成图片的方法里加上就可以了):

//设置画布背景色为白色,即自定义控件显示的背景色为白色:
        canvas.drawRGB(255,255,255);

1.普通的截屏方法

/**
     * 截屏
     *
     * @param activity
     * @return
     */
    public static Bitmap activityShot(Activity activity) 
       /*获取windows中最顶层的view*/
        View view = activity.getWindow().getDecorView();
        //允许当前窗口保存缓存信息
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();
        //获取状态栏高度
        Rect rect = new Rect();
        view.getWindowVisibleDisplayFrame(rect);
        int statusBarHeight = rect.top;
        WindowManager windowManager = activity.getWindowManager();
        //获取屏幕宽和高
        DisplayMetrics outMetrics = new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(outMetrics);
        int width = outMetrics.widthPixels;
        int height = outMetrics.heightPixels;
        //去掉状态栏
        Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, statusBarHeight, width, height - statusBarHeight);
        //销毁缓存信息
        view.destroyDrawingCache();
        view.setDrawingCacheEnabled(false);
        return bitmap;
    

2.截取scrollView的屏幕方法

/**
     * 截取scrollview的屏幕
     * @param scrollView
     * @return
     */
    public static Bitmap getBitmapByView(ScrollView scrollView) 
        int h = 0;
        Bitmap bitmap = null;
        // 获取listView实际高度
        for (int i = 0; i < scrollView.getChildCount(); i++) 
            h += scrollView.getChildAt(i).getHeight();
            scrollView.getChildAt(i).setBackgroundResource(R.drawable.white_drawable);
        

        // 创建对应大小的bitmap
        bitmap = Bitmap.createBitmap(scrollView.getWidth(), h,
                Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        scrollView.draw(canvas);
        // 测试输出
        FileOutputStream out = null;
        try 
            out = new FileOutputStream("/sdcard/screen_test.png");
         catch (FileNotFoundException e) 
            e.printStackTrace();
        
        try 
            if (null != out) 
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
                out.flush();
                out.close();
            
         catch (IOException e) 
            // TODO: handle exception
        
        return bitmap;
    

3.截取ListvListView的屏幕方法

/**
     *  截图listview
     * **/
    public static Bitmap getbBitmap(ListView listView) 
        int h = 0;
        Bitmap bitmap = null;
        // 获取listView实际高度
        for (int i = 0; i < listView.getChildCount(); i++) 
            h += listView.getChildAt(i).getHeight();
        

        // 创建对应大小的bitmap
        bitmap = Bitmap.createBitmap(listView.getWidth(), h,
                Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        listView.draw(canvas);
        // 测试输出
        FileOutputStream out = null;
        try 
            out = new FileOutputStream("/sdcard/screen_test.png");
         catch (FileNotFoundException e) 
            e.printStackTrace();
        
        try 
            if (null != out) 
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
                out.flush();
                out.close();
            
         catch (IOException e) 
            // TODO: handle exception
        
        return bitmap;
    

4.截取线性布局或相对布局屏幕的方法

/**
     * 截取RelativeLayout
     **/
    public static Bitmap getRelativeLayoutBitmap(RelativeLayout relativeLayout) 
        int h = 0;
        Bitmap bitmap;
        for (int i = 0; i < relativeLayout.getChildCount(); i++) 
            h += relativeLayout.getChildAt(i).getHeight();
        
        // 创建对应大小的bitmap
        bitmap = Bitmap.createBitmap(relativeLayout.getWidth(), h,
                Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        relativeLayout.draw(canvas);
        return bitmap;
    
    /**
     * 截取LinearLayout
     **/
    public static Bitmap getLinearLayoutBitmap(LinearLayout linearLayout) 
        int h = 0;
        Bitmap bitmap;
        for (int i = 0; i < linearLayout.getChildCount(); i++) 
            h += linearLayout.getChildAt(i).getHeight();
        
        // 创建对应大小的bitmap
        bitmap = Bitmap.createBitmap(linearLayout.getWidth(), h,
                Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        linearLayout.draw(canvas);
        return bitmap;
    

5.截取除了导航栏之外的整个屏幕

/**
     * 截取除了导航栏之外的整个屏幕
     */
    public static Bitmap screenShotWholeScreen(Activity activity) 
        View dView = activity.getWindow().getDecorView();
        dView.setDrawingCacheEnabled(true);
        dView.buildDrawingCache();
        Bitmap bitmap = Bitmap.createBitmap(dView.getDrawingCache());
        return bitmap;
    

6.拼接合成图片的方法

 //合成三张图片
    private static Bitmap mergeBitmap(Bitmap firstBitmap, Bitmap secondBitmap, Bitmap threeBitmap) 
        Bitmap bitmap = Bitmap.createBitmap(firstBitmap.getWidth(), firstBitmap.getHeight() + secondBitmap.getHeight() + threeBitmap.getHeight(), firstBitmap.getConfig());
        Canvas canvas = new Canvas(bitmap);
        canvas.drawBitmap(firstBitmap, new Matrix(), null);
        canvas.drawBitmap(secondBitmap, 0, firstBitmap.getHeight(), null);
        canvas.drawBitmap(threeBitmap, secondBitmap.getWidth(), firstBitmap.getHeight(), null);
        return bitmap;
    
    //合成两张图片
    public static Bitmap mergeBitmap(Bitmap firstBitmap, Bitmap secondBitmap) 
        Bitmap bitmap = Bitmap.createBitmap(firstBitmap.getWidth(), firstBitmap.getHeight(),firstBitmap.getConfig());
        Canvas canvas = new Canvas(bitmap);
        canvas.drawBitmap(firstBitmap, new Matrix(), null);
        canvas.drawBitmap(secondBitmap, 0, 0, null);
        return bitmap;
    

7.具体的调用和拼接合成图片:

     Bitmap bitmap = StringUtil.activityShot(JCZQTDetailActivity.this);
                Bitmap bitmap = StringUtil.getBitmapByView(scrollView_jc_type);//截取长图
//                Bitmap bitmap = StringUtil.getRelativeLayoutBitmap(rl_jc_all);
//                Bitmap bitmap1 = StringUtil.screenShotWholeScreen(JCZQTDetailActivity.this);
//                Bitmap bitmap2 = ShareUtil.mergeBitmap(bitmap, bitmap1);ShareUtil.weiChatPic(Constants.wx_api, 7, MakeReceiptDetailsActivity.this, bitmap2);

8.微信分享图片方法和链接生成二维码图片方法:

 /**
     * 分享 图片
     */
    public static void weiChatPic(IWXAPI api, int flag, Context context, Bitmap bitmap) 
        if (api.isWXAppInstalled()) //判断微信是否安装
            //Bitmap mWXShareBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.logo_icon);//将截屏得到的bitmap赋值
            GlobalLog.e("sgf", "-------GlobalEntity.USER.getUrl()-------" + GlobalConfig.getURL(context));
            if ("".equals(GlobalConfig.getURL(context)) || "null".equals(GlobalConfig.getURL(context))) 
                Intent intent = new Intent(context, LoginActivity.class);
                context.startActivity(intent);
                return;
            
            loadRQInfo(GlobalConfig.getURL(context));
            Bitmap b = BitmapFactory.decodeResource(context.getResources(), R.drawable.sharewinlogo);
            GlobalLog.e("sgf", "-------bitmap-------" + bitmap);
            GlobalLog.e("sgf", "-------bitmaps-------" + bitmaps);
            GlobalLog.e("sgf", "-------b-------" + b);
            Bitmap bitmap1 = mergeBitmap(bitmap, bitmaps, b);

            WXImageObject imgObject = new WXImageObject(bitmap1);
            //imgObject.imagePath
            WXMediaMessage mediaMessage = new WXMediaMessage();
            mediaMessage.mediaObject = imgObject;

            //设置缩略图
            Bitmap thumbBmp = Bitmap.createScaledBitmap(bitmap1, bitmap1.getWidth() / 10, bitmap1.getHeight() / 10, true);
            mediaMessage.thumbData = bmpToByteArray(thumbBmp, true);
            SendMessageToWX.Req req = new SendMessageToWX.Req();
            req.transaction = buildTransaction("img");//分享类型是图片
            req.message = mediaMessage;
            //表示发送给朋友圈  WXSceneTimeline  表示发送给朋友  WXSceneSession
            req.scene = flag == 7 ? SendMessageToWX.Req.WXSceneSession : SendMessageToWX.Req.WXSceneTimeline;
            api.sendReq(req);
            GlobalLog.e("sgf", "-------end-------");

         else 
            Toast.makeText(context, "您没有安装微信客户端", Toast.LENGTH_SHORT).show();
        
 /**
     * 生成万二维码
     *
     * @param soldUrl
     */
    private static void loadRQInfo(String soldUrl) 
        //回收bitmaps
        if (null != bitmaps && !bitmaps.isRecycled()) 
            bitmaps.recycle();
            bitmaps = null;
        
        try 
            bitmaps = StringUtil.makeQRImage(soldUrl, 480, 350);
         catch (WriterException e) 
            e.printStackTrace();
        
    


9.项目中的StringUtil类,在此分享出来:

package com.yasenagat.yy.rf.util;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Environment;
import android.text.Html;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.ScrollView;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.yasenagat.yy.rf.R;
import com.yasenagat.yy.rf.common.GlobalException;
import com.yasenagat.yy.rf.common.GlobalLog;

import static com.yasenagat.yy.rf.R.id.iv_codes_qr;
import static com.yasenagat.yy.rf.R.id.linearLayout;

public class StringUtil 

    private static int counter = 0;
    private static final String TAG = "StringUtil";

    public static boolean isEmpty(String str) 
        if (null == str || str.trim().equals("")) 
            return true;
        
        return false;
    

    public static boolean isEmptyMoney(String str) 
        try 
            if (null == str || str.equals("") || str.equals("0")
                    || str.equals("0.00") || 0 == Double.parseDouble(str)) 
                return true;
            

            if (Double.parseDouble(str) == 0) 
                return true;
            
         catch (NumberFormatException e) 
            e.printStackTrace();
            return false;
        
        return false;
    

    // String five_before = hitlistsBean.getHit_five().substring(0, hitlistsBean.getHit_five().indexOf(":"));
    //String five_after = hitlistsBean.getHit_five().substring(hitlistsBean.getHit_five().indexOf(":") + 1, hitlistsBean.getHit_five().length());

    /**
     * 截取字符串的前半截
     *
     * @param str
     * @return
     */
    public static String subStringBefore(String str) 
        String beforeStr = str.substring(0, str.indexOf(":"));
        return beforeStr;
    

    /**
     * 截取字符串的后半截
     *
     * @param str
     * @return
     */
    public static String subStringAfter(String str) 
        String afterStr = str.substring(str.indexOf(":") + 1, str.length());
        return afterStr;
    

    /**
     * 截取":" 字符的字符串数组
     *
     * @param str
     * @return 返回一个数组
     */
    public static String[] subStringArray_one(String str) 
        String StrArray[] = str.split(":");

        return StrArray;
    

    /**
     * 截取":" 字符的字符串数组
     *
     * @param str
     * @return 返回一个数组
     */
    public static String[] subStringArray_two(String str) 
        String[] StrArray = str.split(",");
        return StrArray;
    

    /**
     * 截取":" 字符的字符串数组
     *
     * @param str
     * @return 返回一个数组
     */
    public static String[] subStringArray_three(String str) 
        String[] StrArray = subStringArray_two(str);
        //for()
        return StrArray;
    

    /**
     * 判断str1中包含str2的个数
     *
     * @param str1
     * @param str2
     * @return counter
     */
    public static int countStr(char str1, String str2) 

        int countss = 0;
        for (int i = 0; i < str2.length(); i++) 
            if (str2.charAt(i) == str1) 
                countss++;
            
        

        return countss;
    

    /**
     * 截取()中的字符串
     *
     * @param str2
     * @return counter
     */
    public static String countStrParentheses(String str2) 
        // TODO SGF ADD
        String str1 = "";
        Pattern pattern = Pattern.compile("(?<=\\\\()[^\\\\)]+");
        Matcher matcher = pattern.matcher(str2);
        while (matcher.find()) 
            str1 = matcher.group();
        

        return str1;
    

    /**
     * 1:0:1:0:1
     *
     * @param
     * @return
     */

    public static String[] getCount(String a, String b) 
        String[] arrayStr = ;
        arrayStr = a.split(b);
        return arrayStr;
    

    public static boolean isZero(String m1) 
        if (isEmpty(m1)) 
            m1 = "0";
        
        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal("0");
        return b1.compareTo(b2) == 0;
    

    // public static boolean checkPhone(String phone) 
    // if (null == phone || !phone.matches("^1[3-8]+\\\\d9")) 
    // return false;
    // 
    // return true;
    // 
    //
    // public static boolean checkPassword(String password) 
    // if (null == password || !password.matches("[a-f A-F 0-9]6-15")) 
    // return false;
    // 
    // return true;
    // 

    /**
     * 将分转化为元
     *
     * @param str
     * @return fen->yuan 112000(分)-> 1120.00(元)
     */
    public static String formatMoney_FenToYuan(String str) 

        try 
            GlobalLog.d(TAG, "formatMoney_FenToYuan : " + str);
            if (!isEmpty(str)) 
                if (str.equals("0")) 
                    return "0";
                 else if (str.endsWith("00")) 
                    GlobalLog.d(TAG, str.substring(0, str.length() - 2));
                    return new BigDecimal(str.substring(0, str.length() - 2))
                            .toString();
                 else 
                    return BigDecimal.valueOf(new BigDecimal(str).longValue(),
                            2).toString();
                
            

         catch (Exception e) 
            GlobalException.proxy.handle(e, null);
        

        return "0.00";
    

    /**
     * @param str
     * @return 5->5.00
     */
    public static String formatMoney_Yuan(String str) 

        if (!isEmpty(str)) 
            return str + ".00";
        

        return "0.00";
    

    /**
     * @param str
     * @return 5->500
     */
    public static String formatMoney_Fen(String str) 

        if (!isEmpty(str)) 

            double ret = Double.valueOf(str) * 100;
            return String.valueOf((int) ret);
//            return str + "00";
        

        return "0";
    

    /**
     * @param str
     * @return 5->500
     */
    public static String format_FenToYuan(String str) 
        //TODO SGF  ADD

        if (!isEmpty(str)) 

            double ret = Double.valueOf(str) / 100;
            return String.valueOf((int) ret);
//            return str + "00";
        

        return "0";
    

    /**
     * @param m1
     * @param m2
     * @return true m1 > m2
     */
    public static boolean greaterThanMoney(String m1, String m2) 
        if (isEmpty(m1)) 
            m1 = "0";
        
        if (isEmpty(m2)) 
            m2 = "0";
        
        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal(m2);
        return b1.compareTo(b2) == 1;
    

    /**
     * @param m1
     * @param m2
     * @return
     */
    public static boolean greaterEqualMoney(String m1, String m2) 
        if (isEmpty(m1)) 
            m1 = "0";
        
        if (isEmpty(m2)) 
            m2 = "0";
        
        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal(m2);
        return b1.compareTo(b2) == 1 || b1.compareTo(b2) == 0;
    

    public static boolean greaterThanZero(String m1) 
        return greaterThan(m1, "0");
    

    public static boolean greaterEqualZero(String m1) 
        return greaterEqual(m1, "0");
    

    public static boolean greaterEqual(String m1, String m2) 
        if (isEmpty(m1)) 
            m1 = "0";
        

        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal(m2);
        return b1.compareTo(b2) == 1 || b1.compareTo(b2) == 0;
    

    public static boolean greaterThan(String m1, String m2) 
        if (isEmpty(m1)) 
            m1 = "0";
        

        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal(m2);
        return b1.compareTo(b2) == 1 || b1.compareTo(b2) == 0;
    

    public static boolean lessEqualZero(String m1) 
        return lessEqual(m1, "0");
    

    public static boolean lessThanZero(String m1) 
        return lessThan(m1, "0");
    

    public static boolean lessEqual(String m1, String m2) 
        if (isEmpty(m1)) 
            m1 = "0";
        
        if (isEmpty(m2)) 
            m2 = "0";
        
        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal(m2);
        return b1.compareTo(b2) == -1 || b1.compareTo(b2) == 0;
    

    /**
     * 比较大小
     *
     * @param m1
     * @param m2
     * @return
     */
    public static boolean lessThan(String m1, String m2) 
        if (isEmpty(m1)) 
            m1 = "0";
        
        if (isEmpty(m2)) 
            m2 = "0";
        
        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal(m2);
        return b1.compareTo(b2) == -1;
    

    /**
     * @param m1
     * @param m2
     * @return true m1 >= m2
     */
    public static boolean compareMoneyEqual(String m1, String m2) 
        if (isEmpty(m1)) 
            m1 = "0";
        
        if (isEmpty(m2)) 
            m2 = "0";
        
        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal(m2);
        return b1.compareTo(b2) == 1 || b1.compareTo(b2) == 0;
    

    public static boolean isMoneyZero(String m1) 
        if (isEmpty(m1)) 
            m1 = "0";
        
        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal("0");
        return b1.compareTo(b2) == 0;
    

    /**
     * 加法运算
     *
     * @param m1 "1.7"
     * @param m2 "7.85"
     * @return "1.7" + "7.85" => 9.55
     */
    public static String add(String m1, String m2) 
        if (isEmpty(m1)) 
            m1 = "0";
        
        if (isEmpty(m2)) 
            m2 = "0";
        
        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal(m2);
        return b1.add(b2).toString();
    

    public static String add(String m1, String m2, String m3) 
        if (isEmpty(m1)) 
            m1 = "0";
        
        if (isEmpty(m2)) 
            m2 = "0";
        
        if (isEmpty(m3)) 
            m3 = "0";
        
        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal(m2);
        BigDecimal b3 = new BigDecimal(m3);
        return b1.add(b2).add(b3).toString();
    

    /**
     * 减法运算
     *
     * @param m1 2.3
     * @param m2 1.1
     * @return 1.2
     */
    public static String subtract(String m1, String m2) 
        if (isEmpty(m1)) 
            m1 = "0";
        
        if (isEmpty(m2)) 
            m2 = "0";
        
        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal(m2);
        return b1.subtract(b2).toString();
    

    /**
     * @param m1
     * @param m2
     * @return
     */
    public static String divide(String m1, String m2) 
        GlobalLog.d(TAG, "m1 : " + m1 + " m2 : " + m2);
        if (isEmpty(m1)) 
            m1 = "0";
        
        if (isEmpty(m2)) 
            m2 = "1";
        
        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal(m2);
        return b1.divide(b2, 2, RoundingMode.HALF_UP).toString();
    

    /**
     * 乘法运算
     *
     * @param m1 参数1
     * @param m2 参数2
     * @return 两个数相乘
     */
    public static String multiply(String m1, String m2) 
        GlobalLog.d(TAG, "m1 : " + m1 + " ; m2 : " + m2);
        if (isEmpty(m1)) 
            m1 = "0";
        
        if (isEmpty(m2)) 
            m2 = "0";
        
        BigDecimal b1 = new BigDecimal(m1);
        BigDecimal b2 = new BigDecimal(m2);
        return b1.multiply(b2).toString();
    

    public static String getProgress(int progress) 
        return (progress / 20) + "";
    

    public static String getLimit(String total_unit) 
        double l = Double.valueOf(total_unit);
        return (int) Math.max(Math.round(l * 0.05), 1) + "";
    

    private static BigDecimal one_hundred_million = new BigDecimal("100000000");
    private static BigDecimal ten_thousand = new BigDecimal("10000");
    private static BigDecimal hundred_thousand = new BigDecimal("100000");
    private static BigDecimal one_million = new BigDecimal("1000000");
    private static BigDecimal ten_million = new BigDecimal("10000000");

    public static String formatMoney_Chinese(String m) 

        BigDecimal b1 = new BigDecimal(m);
        BigDecimal b2 = b1.divide(one_hundred_million);

        if (b2.doubleValue() >= 10) 
            return b2.toString() + "亿";
         else if (b2.doubleValue() >= 1) 
            return b1.divide(one_hundred_million).setScale(2,
                    RoundingMode.HALF_DOWN)
                    + "亿";
         else 
            b2 = b1.divide(ten_million);
            if (b2.intValue() > 0) 
                System.out.println(b2);
                return b2.multiply(new BigDecimal(1000))
                        .setScale(1, RoundingMode.HALF_DOWN).toString()
                        + "万";

             else 
                b2 = b1.divide(one_million);
                if (b2.intValue() > 0) 

                    return b2.multiply(new BigDecimal(100))
                            .setScale(0, RoundingMode.HALF_DOWN).toString()
                            + "万";
                 else 
                    b2 = b1.divide(hundred_thousand);
                    if (b2.intValue() > 0) 
                        return b2.multiply(new BigDecimal(10))
                                .setScale(0, RoundingMode.HALF_DOWN).toString()
                                + "万";
                     else 
                        b2 = b1.divide(ten_thousand);
                        if (b2.intValue() > 0) 
                            return b2.setScale(2, RoundingMode.HALF_DOWN) + "万";
                        
                        return m + "元";
                    
                
            
        

    

    /**
     * 是否显示出票明细
     *
     * @param issueState
     * @return
     */
    public static boolean isCanShowTicket(String issueState) 

        try 

            if (issueState.startsWith("QC@")) 
                String str = issueState.substring(3);
                if (Integer.parseInt(str) > 1) 
                    return true;
                
                GlobalLog.d(TAG, str);
             else 
                return false;
            

         catch (Exception e) 
            e.printStackTrace();
        

        return false;
    

    public static boolean verifyPassword(String password) 

        if (isEmpty(password)) 
            return false;
         else if (password.matches("^[0-9a-zA-Z]6,15$")) 
            return true;
        

        return false;
    

    /**
     * 手机号码验证
     */
    public static boolean verifyPhoneNumber(String phone) 
        if (isEmpty(phone)) 
            return false;
         else if (!phone.startsWith("1")) 
            return false;
         else if (phone.length() == 11) 
            return true;
        
//        if (phone
//                .matches("^((13[0-9])|(15[^4,\\\\D])|(18[0,5-9])|(170))\\\\d8$")) 
//            return true;
//        

        return false;
    

    // /** 数字验证 */
    // public static boolean verifyNumeric(String paramString) 
    // return Pattern.compile("[0-9]*").matcher(paramString).matches();
    // 

    /**
     * 用户名验证 "^[^0-9]\\\\w3,16$"
     */
    public static boolean verifyUsername(String username) 
        if (isEmpty(username)) 
            return false;
         else if (username.matches("[\\u4E00-\\u9FA5a-zA-Z]1[\\u4E00-\\u9FA5a-zA-Z0-9]2,15")) 
            return true;
        
        return false;
    

    // /** 密码验证 */
    // public static boolean verifyPassword(String paramString) 
    // return Pattern.compile("^[A-Za-z0-9]6,16$")
    // .matcher(paramString)
    // .matches();
    // 

    public static boolean verifyEmail(String paramString) 
        return Pattern
                .compile("^\\\\s*([A-Za-z0-9_-]+(\\\\.\\\\w+)*@(\\\\w+\\\\.)+\\\\w+)\\\\s*$")
                .matcher(paramString).matches();
    

    public static boolean verifyIDCard(String validateStr) 
        String regex = "(^\\\\d15$)|(^\\\\d18$)|(^\\\\d17(\\\\d|X|x)$)";
        Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
        if (!pattern.matcher(validateStr).matches()) 
            return false;
        
        return true;
    


    /**
     * 校验密码有 6到18位 字母和数字 组成 的正则
     *
     * @param passStr
     * @return
     */
    public static boolean verifyPasswordLen(String passStr) 
        String regex = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]6,18$";
        return passStr.matches(regex);
    
    /**
     * 校验输入框小数点后面只能有2位数字 的正则
     *
     * @param passStr
     * @return
     */
    public static boolean verifTwo(String passStr) 
        String regex = "^\\\\d1,8(\\\\.\\\\d1,2)?$";
        return passStr.matches(regex);
    

    /**
     * 校验输入框只能输入数字 的正则
     *
     * @param passStr
     * @return
     */
    public static boolean verifNum(String passStr) 
        String regex = "^[0-9]*[1-9][0-9]*$";
        return passStr.matches(regex);
    

    public static String getLast_2(String issue) 
        if (StringUtil.isEmpty(issue)) 
            return "";
         else 
            return issue.substring(issue.length() - 2);
        
    

    public static String getLast_3(String issue) 
        if (StringUtil.isEmpty(issue)) 
            return "";
         else 
            return issue.substring(issue.length() - 3);
        
    

    public static Map<String, String> parseData(String data) 

        Map<String, String> retMap = new HashMap<String, String>();

        String[] items = data.split("#");
        String[] item = null;
        for (String i : items) 
            item = i.split("=");
            if (item.length >= 2) 
                retMap.put(item[0], item[1]);
            
        

//        for (String key : retMap.keySet()) 
//            System.out.println(key + " : " + retMap.get(key));
//        

        return retMap;
    

    public static String setStrRed(String str) 
        return "<font color='red'>" + str + "</font>";
    
    //白色
    public static String setStrWhite(String str) 
        return "<font color='#ffffff'>" + str + "</font>";
    

    public static String setStrMainRed(String str) 
        return "<font color='#f33b3b'>" + str + "</font>";
    
    //灰色
    public static String setStrGray(String str) 
        return "<font color='#888888'>" + str + "</font>";
    

    public static String setStrOrenge(String str) 
        return "<font color='#ff9e14'>" + str + "</font>";
    
    //竞彩篮球-绿色
    public static String setStrGreen(String str) 
        return "<font color='#4bc160'>" + str + "</font>";
    

    public static String setStrBlack(String str) 
        return "<font color='#313131'>" + str + "</font>";
    

    public static String setStrOrange(String str) 
        return "<font color='#FF3D3D'>" + str + "</font>";
    

    public static String setStrRedBold(String str) 
        return "<b><font color='red'>" + str + "</font></>";
    

    public static String setStrBold(String str) 
        return "<b>" + str + "</b>";
    

    public static List<String> getNumList(int start, int end) 

        List<String> list = new ArrayList<String>();
        for (int i = start; i <= end; i++) 
            list.add(i + "");
        
        return list;
    

    public static String setWinCodeRed(String drawCode, String code) 

        StringBuffer retBuf = new StringBuffer("");

        String[] drawCodes = drawCode.split(",");

        List<String> codes = new ArrayList<String>();

        for (String c : code.split(",")) 
            codes.add(c);
        

        for (String d : drawCodes) 

            if (codes.contains(d)) 
                retBuf.append(setStrRed(d));
                retBuf.append(" ");
             else 
                retBuf.append(d);
                retBuf.append(" ");
            
        

        return retBuf.toString();
    

    public static boolean isNumeric(String str) 
        Pattern pattern = Pattern.compile("[0-9]*");
        Matcher isNum = pattern.matcher(str);
        if (!isNum.matches()) 
            return false;
        
        return true;
    

    public static String getString(String str) 
        if (isEmpty(str)) 
            return "";
        
        return str.trim();
    

    /**
     * 生成创建二维码
     */
    public static Bitmap createQRImage(String url, int QR_WIDTH, int QR_HEIGHT) throws WriterException 

        // 判断URL合法性
        if (url == null || "".equals(url) || url.length() < 1) 
            return null;
        
        Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
        // 图像数据转换,使用了矩阵转换
        BitMatrix bitMatrix = new QRCodeWriter().encode(url,
                BarcodeFormat.QR_CODE, QR_WIDTH, QR_HEIGHT, hints);
        int[] pixels = new int[QR_WIDTH * QR_HEIGHT];
        // 下面这里按照二维码的算法,逐个生成二维码的图片,
        // 两个for循环是图片横列扫描的结果
        for (int y = 0; y < QR_HEIGHT; y++) 
            for (int x = 0; x < QR_WIDTH; x++) 
                if (bitMatrix.get(x, y)) 
                    pixels[y * QR_WIDTH + x] = 0xff000000;
                 else 
                    // pixels[y * QR_WIDTH + x] = 0xffffffff;
                
            
        
        // 生成二维码图片的格式,使用ARGB_8888
        Bitmap bitmap = Bitmap.createBitmap(QR_WIDTH, QR_HEIGHT,
                Bitmap.Config.ARGB_8888);
        bitmap.setPixels(pixels, 0, QR_WIDTH, 0, 0, QR_WIDTH, QR_HEIGHT);
        //TODO WH 显示到一个ImageView上面
        //iv_codes_qr.setImageBitmap(bitmap);
        return bitmap;

    

    /**
     * 根据指定内容生成自定义宽高的二维码图片
     *
     * @param content 需要生成二维码的内容
     * @param width   二维码宽度
     * @param height  二维码高度
     * @throws WriterException 生成二维码异常
     */
    public static Bitmap makeQRImage(String content, int width, int height)
            throws WriterException 
        // 判断URL合法性
        if (!isNoBlankAndNoNull(content))
            return null;

        Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        // 图像数据转换,使用了矩阵转换
        BitMatrix bitMatrix = new QRCodeWriter().encode(content,
                BarcodeFormat.QR_CODE, width, height, hints);
        int[] pixels = new int[width * height];
        // 按照二维码的算法,逐个生成二维码的图片,两个for循环是图片横列扫描的结果
        for (int y = 0; y < height; y++) 
            for (int x = 0; x < width; x++) 
                if (bitMatrix.get(x, y))
                    pixels[y * width + x] = 0xff000000;
                else 
                    pixels[y * width + x] = 0xffffffff;
                
            
        
        // 生成二维码图片的格式,使用ARGB_8888
        Bitmap bitmap = Bitmap.createBitmap(width, height,
                Bitmap.Config.ARGB_8888);
        bitmap.setPixels(pixels, 0, width, 0, 0, width, height);

        return bitmap;
    

    /**
     * 判断字符串是否非空非null
     *
     * @param strParm 需要判断的字符串
     * @return 真假
     */
    public static boolean isNoBlankAndNoNull(String strParm) 
        return !((strParm == null) || (strParm.equals("")));
    


    /**
     * 指定目录写入文件内容
     *
     * @param filePath 文件路径+文件名
     * @param
     * @throws IOException
     */
    public static void saveAsJPEG(Bitmap bitmap, String filePath)
            throws IOException 
        FileOutputStream fos = null;

        try 
            File file = new File(filePath);
            if (!file.getParentFile().exists()) 
                file.getParentFile().mkdirs();
            
            fos = new FileOutputStream(file);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            fos.flush();
         finally 
            if (fos != null) 
                fos.close();
            
        
    

    /**
     * 是否有内存
     *
     * @return
     */
    public static boolean isMountedSDCard() 
        if (Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())) 
            return true;
         else 
            return false;
        
    

    /**
     * 截屏
     *
     * @param activity
     * @return
     */
    public static Bitmap activityShot(Activity activity) 
       /*获取windows中最顶层的view*/
        View view = activity.getWindow().getDecorView();
        //允许当前窗口保存缓存信息
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();
        //获取状态栏高度
        Rect rect = new Rect();
        view.getWindowVisibleDisplayFrame(rect);
        int statusBarHeight = rect.top;
        WindowManager windowManager = activity.getWindowManager();
        //获取屏幕宽和高
        DisplayMetrics outMetrics = new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(outMetrics);
        int width = outMetrics.widthPixels;
        int height = outMetrics.heightPixels;
        //去掉状态栏
        Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, statusBarHeight, width, height - statusBarHeight);
        //销毁缓存信息
        view.destroyDrawingCache();
        view.setDrawingCacheEnabled(false);
        return bitmap;
    

    /**
     * 截取scrollview的屏幕
     * @param scrollView
     * @return
     */
    public static Bitmap getBitmapByView(ScrollView scrollView) 
        int h = 0;
        Bitmap bitmap = null;
        // 获取listView实际高度
        for (int i = 0; i < scrollView.getChildCount(); i++) 
            h += scrollView.getChildAt(i).getHeight();
            scrollView.getChildAt(i).setBackgroundResource(R.drawable.white_drawable);
        

        // 创建对应大小的bitmap
        bitmap = Bitmap.createBitmap(scrollView.getWidth(), h,
                Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        scrollView.draw(canvas);
        // 测试输出
        FileOutputStream out = null;
        try 
            out = new FileOutputStream("/sdcard/screen_test.png");
         catch (FileNotFoundException e) 
            e.printStackTrace();
        
        try 
            if (null != out) 
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
                out.flush();
                out.close();
            
         catch (IOException e) 
            // TODO: handle exception
        
        return bitmap;
    
    /**
     *  截图listview
     * **/
    public static Bitmap getbBitmap(ListView listView) 
        int h = 0;
        Bitmap bitmap = null;
        // 获取listView实际高度
        for (int i = 0; i < listView.getChildCount(); i++) 
            h += listView.getChildAt(i).getHeight();
        

        // 创建对应大小的bitmap
        bitmap = Bitmap.createBitmap(listView.getWidth(), h,
                Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        listView.draw(canvas);
        // 测试输出
        FileOutputStream out = null;
        try 
            out = new FileOutputStream("/sdcard/screen_test.png");
         catch (FileNotFoundException e) 
            e.printStackTrace();
        
        try 
            if (null != out) 
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
                out.flush();
                out.close();
            
         catch (IOException e) 
            // TODO: handle exception
        
        return bitmap;
    
    /**
     * 截取RelativeLayout
     **/
    public static Bitmap getRelativeLayoutBitmap(RelativeLayout relativeLayout) 
        int h = 0;
        Bitmap bitmap;
        for (int i = 0; i < relativeLayout.getChildCount(); i++) 
            h += relativeLayout.getChildAt(i).getHeight();
        
        // 创建对应大小的bitmap
        bitmap = Bitmap.createBitmap(relativeLayout.getWidth(), h,
                Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        relativeLayout.draw(canvas);
        return bitmap;
    
    /**
     * 截取LinearLayout
     **/
    public static Bitmap getLinearLayoutBitmap(LinearLayout linearLayout) 
        int h = 0;
        Bitmap bitmap;
        for (int i = 0; i < linearLayout.getChildCount(); i++) 
            h += linearLayout.getChildAt(i).getHeight();
        
        // 创建对应大小的bitmap
        bitmap = Bitmap.createBitmap(linearLayout.getWidth(), h,
                Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        linearLayout.draw(canvas);
        return bitmap;
    
    /**
     * 截取除了导航栏之外的整个屏幕
     */
    public static Bitmap screenShotWholeScreen(Activity activity) 
        View dView = activity.getWindow().getDecorView();
        dView.setDrawingCacheEnabled(true);
        dView.buildDrawingCache();
        Bitmap bitmap = Bitmap.createBitmap(dView.getDrawingCache());
        return bitmap;
    
 



以上是关于追求截图的极致-牛牛截图再添新成员-截长图功能的主要内容,如果未能解决你的问题,请参考以下文章

Macbook怎么滚动截屏(截长图)

网页截长图分享和保存iOS

利用 Python + Selenium 实现对页面的指定元素截图(可截长图元素)

Chrome进行长截图

c#利用bitblt如何获取最小化后的窗口截图

c#中使用CopyFromScreen截图为啥截不到图片???