android长截屏原理及实现代码

作者:Android笔记 时间:2021-11-13 05:55:50 

小米系统自带的长截屏应该很多人都用过,效果不错。当长截屏时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长截屏原理及实现代码 

可以看到毫无拼接痕迹。

来源:https://android-notes.github.io/2016/12/03/android%E9%95%BF%E6%88%AA%E5%B1%8F%E5%8E%9F%E7%90%86/?utm_source=tuicool&utm_medium=referral

标签:android,长截屏
0
投稿

猜你喜欢

  • c#通过进程调用cmd判断登录用户权限代码分享

    2021-07-24 00:53:26
  • Java实现简单班级管理系统

    2023-01-07 16:20:29
  • C#类的多态性详解

    2022-04-27 02:55:42
  • Android Studio中统一管理版本号引用配置问题

    2023-03-06 04:23:54
  • Java中BigDecimal类的add()的使用详解

    2023-03-07 16:12:11
  • winform 实现控制输入法

    2022-05-01 11:35:38
  • Java单例模式下的MongoDB数据库操作工具类

    2023-11-20 12:55:01
  • java中ResultSet遍历数据操作

    2022-06-13 05:02:13
  • Android组件化开发路由的设计实践

    2021-06-20 00:27:24
  • Java编程之双重循环打印图形

    2022-02-01 22:06:37
  • 构建多模块的Spring Boot项目步骤全纪录

    2022-02-20 04:28:44
  • linux下C语言中的mkdir函数与rmdir函数

    2023-07-07 14:16:16
  • C#定义简单的反射工厂实例分析

    2021-08-21 15:16:16
  • 基于java实现简单发红包功能

    2023-08-09 17:06:40
  • 漂亮的Android音乐歌词控件 仿网易云音乐滑动效果

    2022-01-26 14:16:25
  • Android实现每天定时提醒功能

    2021-10-18 09:52:33
  • JAVA实现扫描线算法(超详细)

    2023-06-30 13:33:20
  • Android开发InputManagerService创建与启动流程

    2021-07-11 20:45:22
  • Android Studio实现标题栏和状态栏的隐藏

    2022-11-29 08:45:10
  • Java下变量大小写驼峰、大小写下划线、大小写连线转换

    2022-04-19 15:20:18
  • asp之家 软件编程 m.aspxhome.com