android长截屏原理及实现代码

发布时间 - 2026-01-11 02:53:16    点击率:

小米系统自带的长截屏应该很多人都用过,效果不错。当长截屏时listview就会自动滚动,当按下停止截屏时,就会得到一张完整的截屏。

该篇就介绍一下长截屏的原理

上篇中介绍了android屏幕共享实现方式,该篇的原理和上一篇基本一致。

获取view影像

当我们想得到一个view的影像时,我们可以调用系统api,得到view的bitmap,但有时可能得不到。我们可以通过另一种方式得到。

首先创建一个和view一样大小的bitmap

复制代码 代码如下:
Bitmap bmp = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.RGB_565);

然后把view绘制到bmp上

Canvas canvas = new Canvas(); 
 
canvas.setBitmap(bmp); 
 
view.draw(canvas);

执行完上面代码后bmp上就是view的影像了。

制造滚动事件,促使view滚动

我们可以创建一个MotionEvent,然后定时修改MotionEvent的y值,并分发给view,从而促使view上下滚动。当然我们也可以定时修改x值促使view左右滚动。

代码大致如下

final MotionEvent motionEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, view.getWidth() / 2, view.getHeight() / 2, 0);  
    
view.postDelayed(new Runnable() { 
      @Override 
      public void run() { 
        
        motionEvent.setAction(MotionEvent.ACTION_MOVE); 
 
        motionEvent.setLocation((int) motionEvent.getX(), (int) motionEvent.getY() - 1); 
    //把事件分发给view
        view.dispatchTouchEvent(motionEvent); 
        
        view.postDelayed(this, DELAY); 
      } 
   }, DELAY);

注意:从分发DOWN事件到结束都要使用同一个MotionEvent对象,只需要不断改变x或y值。

每次x或y的值相对于上次改动不能过大,若过大,view实际滚动距离可能达不到为MotionEvent设置的值(因view滚动时卡顿导致)。

截屏

当为MotionEvent设置的x或y值正好时当前view的大小时,创建新的bitmap,通过上述方法把view绘制到bitmap上,想要停止截屏时拼接所有bitmap即可。

备注

当我们想要把Listview长截屏时,需要为ListView外面嵌套一层和ListView一样大小的View,以上的所有操作都在嵌套的这层view上操作。当我们调用嵌套的这层view的draw(new Canvas(bmp))时会把当前看到的这块ListView绘制到bmp上,不管ListView嵌套了多少层子view都可以绘制到当前bmp上。

由于ListView中根据滑动的距离是否大于ViewConfiguration.get(view.getContext()).getScaledTouchSlop() )来确定要不要滚动,所以一开始我们要特殊处理下,为什么是ViewConfiguration.get(view.getContext()).getScaledTouchSlop() )可以查看ListView的事件分发相关函数得到(dispatchTouchEvent),让Listview认为是开始滚动,这样才能保证以后分发的滑动距离和实际滚动距离一致。

Listview也要通知是否滚动到了最后,不然如果没有手动停止的话,虽然还是在一直分发滚动事件,但ListView不再滚动,导致最终截图后后面全是重复的最后一屏幕。

附 实现大致方式代码,有待优化

package com.example.wanjian.test;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Environment;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.LinearLayout;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by wanjian on 16/8/18.
 */
public class ScrollableViewRECUtil {
  public static final int VERTICAL = 0;
  private static final int DELAY = 2;
  private List<Bitmap> bitmaps = new ArrayList<>();
  private int orientation = VERTICAL;
  private View view;
  private boolean isEnd;
  private OnRecFinishedListener listener;
  public ScrollableViewRECUtil(View view, int orientation) {
    this.view = view;
    this.orientation = orientation;
  }
  public void start(final OnRecFinishedListener listener) {
    this.listener = listener;
    final MotionEvent motionEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, view.getWidth() / 2, view.getHeight() / 2, 0);
    view.dispatchTouchEvent(motionEvent);
    motionEvent.setAction(MotionEvent.ACTION_MOVE);
    //滑动距离大于ViewConfiguration.get(view.getContext()).getScaledTouchSlop()时listview才开始滚动
    motionEvent.setLocation(motionEvent.getX(), motionEvent.getY() - (ViewConfiguration.get(view.getContext()).getScaledTouchSlop() + 1));
    view.dispatchTouchEvent(motionEvent);
    motionEvent.setLocation(motionEvent.getX(), view.getHeight() / 2);
    view.postDelayed(new Runnable() {
      @Override
      public void run() {
        if (isEnd) {
          //停止时正好一屏则全部绘制,否则绘制部分
          if ((view.getHeight() / 2 - (int) motionEvent.getY()) % view.getHeight() == 0) {
            Bitmap bitmap = rec();
            bitmaps.add(bitmap);
          } else {
            Bitmap origBitmap = rec();
            int y = view.getHeight() / 2 - (int) motionEvent.getY();
            Bitmap bitmap = Bitmap.createBitmap(origBitmap, 0, view.getHeight() - y % view.getHeight(), view.getWidth(), y % view.getHeight());
            bitmaps.add(bitmap);
            origBitmap.recycle();
          }
          //最后一张可能高度不足view的高度
          int h = view.getHeight() * (bitmaps.size() - 1);
          Bitmap bitmap = bitmaps.get(bitmaps.size() - 1);
          h = h + bitmap.getHeight();
          Bitmap result = Bitmap.createBitmap(view.getWidth(), h, Bitmap.Config.RGB_565);
          Canvas canvas = new Canvas();
          canvas.setBitmap(result);
          for (int i = 0; i < bitmaps.size(); i++) {
            Bitmap b = bitmaps.get(i);
            canvas.drawBitmap(b, 0, i * view.getHeight(), null);
            b.recycle();
          }
          listener.onRecFinish(result);
          return;
        }
        if ((view.getHeight() / 2 - (int) motionEvent.getY()) % view.getHeight() == 0) {
          Bitmap bitmap = rec();
          bitmaps.add(bitmap);
        }
        motionEvent.setAction(MotionEvent.ACTION_MOVE);
  //模拟每次向上滑动一个像素,这样可能导致滚动特别慢,实际使用时可以修改该值,但判断是否正好滚动了
  //一屏幕就不能简单的根据 (view.getHeight() / 2 - (int) motionEvent.getY()) % view.getHeight() == 0 来确定了。
  //可以每次滚动n个像素,当发现下次再滚动n像素时就超出一屏幕时可以改变n的值,保证下次滚动后正好是一屏幕,
  //这样就可以根据(view.getHeight() / 2 - (int) motionEvent.getY()) % view.getHeight() == 0来判断要不要截屏了。
        motionEvent.setLocation((int) motionEvent.getX(), (int) motionEvent.getY() - 1);
        view.dispatchTouchEvent(motionEvent);
        view.postDelayed(this, DELAY);
      }
    }, DELAY);
  }
  public void stop() {
    isEnd = true;
  }
  private Bitmap rec() {
    Bitmap film = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.RGB_565);
    Canvas canvas = new Canvas();
    canvas.setBitmap(film);
    view.draw(canvas);
    return film;
  }
  public interface OnRecFinishedListener {
    void onRecFinish(Bitmap bitmap);
  }
}

activity代码

 setContentView(R.layout.activity_main4);
//
    listview= (ListView) findViewById(R.id.listview);
    listview.setAdapter(new BaseAdapter() {
      @Override
      public int getCount() {
        return 100;
      }
      @Override
      public Object getItem(int position) {
        return null;
      }
      @Override
      public long getItemId(int position) {
        return 0;
      }
      @Override
      public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView==null){
          Button button= (Button) LayoutInflater.from(getApplication()).inflate(R.layout.item,listview,false);
          button.setText(""+position);
          return button;
        }
        ((Button)convertView).setText(""+position);
        return convertView;
      }
    });
//
    File file=new File(Environment.getExternalStorageDirectory(),"aaa");
    file.mkdirs();
    for (File f:file.listFiles()){
      f.delete();
    }
    listview.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        listview.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        start();
      }
    });
private void start(){
    final View view=findViewById(R.id.view);
    final ScrollableViewRECUtil scrollableViewRECUtil=new ScrollableViewRECUtil(view,ScrollableViewRECUtil.VERTICAL);
    scrollableViewRECUtil.start(new ScrollableViewRECUtil.OnRecFinishedListener() {
      @Override
      public void onRecFinish(Bitmap bitmap) {
        File f= Environment.getExternalStorageDirectory();
        System.out.print(f.getAbsoluteFile().toString());
        Toast.makeText(getApplicationContext(),f.getAbsolutePath(),Toast.LENGTH_LONG).show();
        try {
          bitmap.compress(Bitmap.CompressFormat.JPEG,60,new FileOutputStream(new File(f,"rec"+System.currentTimeMillis()+".jpg")));
          Toast.makeText(getApplicationContext(),"Success",Toast.LENGTH_LONG).show();
        }catch (Exception e){
          e.printStackTrace();
        }
      }
    });
    // scrollableViewRECUtil
    view.postDelayed(new Runnable() {
      @Override
      public void run() {
        scrollableViewRECUtil.stop();
      }
    },90*1000);
  }

布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:id="@+id/view"
  android:orientation="vertical"
 >
    <ListView
      android:id="@+id/listview"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:divider="#e1e1e1"
      android:dividerHeight="2dp"
      ></ListView>
</LinearLayout>

效果图

屏幕

 

最终截屏

 

可以看到毫无拼接痕迹。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# android长截屏  # android  # 长截屏  # 实现  # 长截屏原理  # Android截屏方案实现原理解析  # Android截屏分享功能  # Android中关于屏幕的三个小众知识(宽屏适配、禁止截屏和保持屏幕常亮)  # Android 下调试手机截屏的方法  # Android 实现截屏功能的实例  # Android实现截屏方式整理(总结)  # android视频截屏&手机录屏实现代码  # 浅谈Android截屏和指定View生成截图  # 我们可以  # 当我们  # 就会  # 过大  # 创建一个  # 这层  # 都在  # 都要  # 也要  # 要不要  # 很多人  # 如果没有  # 可以看到  # 就不能  # 只需要  # 用过  # 时就  # 按下  # 这块  # 在一 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel如何为API编写文档_Laravel API文档生成与维护方法  BootStrap整体框架之基础布局组件  如何用搬瓦工VPS快速搭建个人网站?  如何确认建站备案号应放置的具体位置?  ChatGPT常用指令模板大全 新手快速上手的万能Prompt合集  如何快速重置建站主机并恢复默认配置?  Win11怎么设置虚拟桌面 Win11新建多桌面切换操作【技巧】  C#如何调用原生C++ COM对象详解  Edge浏览器提示“由你的组织管理”怎么解决_去除浏览器托管提示【修复】  Laravel如何实现多表关联模型定义_Laravel多对多关系及中间表数据存取【方法】  如何在阿里云域名上完成建站全流程?  Laravel怎么解决跨域问题_Laravel配置CORS跨域访问  标题:Vue + Vuex + JWT 身份认证的正确实践与常见误区解析  想要更高端的建设网站,这些原则一定要坚持!  宙斯浏览器视频悬浮窗怎么开启 边看视频边操作其他应用教程  佐糖AI抠图怎样调整抠图精度_佐糖AI精度调整与放大细化操作【攻略】  网站制作软件有哪些,制图软件有哪些?  Laravel怎么实现搜索功能_Laravel使用Eloquent实现模糊查询与多条件搜索【实例】  如何在局域网内绑定自建网站域名?  如何选择可靠的免备案建站服务器?  Laravel怎么实现验证码(Captcha)功能  Laravel如何实现本地化和多语言支持?(i18n教程)  音响网站制作视频教程,隆霸音响官方网站?  Laravel如何实现密码重置功能_Laravel密码找回与重置流程  如何快速打造个性化非模板自助建站?  jQuery validate插件功能与用法详解  JavaScript如何实现类型判断_typeof和instanceof有什么区别  微信h5制作网站有哪些,免费微信H5页面制作工具?  英语简历制作免费网站推荐,如何将简历翻译成英文?  Android实现代码画虚线边框背景效果  linux top下的 minerd 木马清除方法  javascript中的try catch异常捕获机制用法分析  Laravel如何发送系统通知_Laravel Notifications实现多渠道消息通知  Laravel distinct去重查询_Laravel Eloquent去重方法  Laravel如何处理和验证JSON类型的数据库字段  javascript中对象的定义、使用以及对象和原型链操作小结  Windows10电脑怎么查看硬盘通电时间_Win10使用工具检测磁盘健康  如何快速建站并高效导出源代码?  Firefox Developer Edition开发者版本入口  实例解析angularjs的filter过滤器  做企业网站制作流程,企业网站制作基本流程有哪些?  Laravel如何实现一对一模型关联?(Eloquent示例)  WordPress 子目录安装中正确处理脚本路径的完整指南  用yum安装MySQLdb模块的步骤方法  Python3.6正式版新特性预览  javascript如何操作浏览器历史记录_怎样实现无刷新导航  武汉网站设计制作公司,武汉有哪些比较大的同城网站或论坛,就是里面都是武汉人的?  Laravel如何使用Sanctum进行API认证?(SPA实战)  阿里云网站搭建费用解析:服务器价格与建站成本优化指南  DeepSeek是免费使用的吗 DeepSeek收费模式与Pro版本功能详解