zoukankan      html  css  js  c++  java
  • 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/   android长截屏原理

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

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

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

    获取view影像

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

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

    1
    Bitmap bmp = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.RGB_565);

    然后把view绘制到bmp上

    1
    2
    3
    4
    5
    6
     
    Canvas canvas = new Canvas();
     
    canvas.setBitmap(bmp);
     
    view.draw(canvas);

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

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

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

    代码大致如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    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不再滚动,导致最终截图后后面全是重复的最后一屏幕。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
     
    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代码
     
    ```java
     
    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();
    }
    });
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
     
    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);
    }

    布局

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    <?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>

    效果图

    • 屏幕
      屏幕屏幕
    • 最终截屏

    截屏截屏

    可以看到毫无拼接痕迹。

    来自我的博客

    http://blog.csdn.net/qingchunweiliang/article/details/52248643

  • 相关阅读:
    PHP做ERP, CRM, CMS系统需要注意哪些地方
    java封装小实例
    java中数组的数组问题
    switch语句小练习
    java交换两个变量值a,b的多钟方法
    java中 i = i++ 的结果
    每日java基础知识(01)
    计算机存储负数以及int转byte时-128的出现
    python RSA 加密
    CentOS 7 安装 建立svn仓库 远程连接
  • 原文地址:https://www.cnblogs.com/onelikeone/p/7091231.html
Copyright © 2011-2022 走看看