android長(zhǎng)截屏原理及實(shí)現(xiàn)代碼
小米系統(tǒng)自帶的長(zhǎng)截屏應(yīng)該很多人都用過,效果不錯(cuò)。當(dāng)長(zhǎng)截屏?xí)rlistview就會(huì)自動(dòng)滾動(dòng),當(dāng)按下停止截屏?xí)r,就會(huì)得到一張完整的截屏。
該篇就介紹一下長(zhǎng)截屏的原理
上篇中介紹了android屏幕共享實(shí)現(xiàn)方式,該篇的原理和上一篇基本一致。
獲取view影像
當(dāng)我們想得到一個(gè)view的影像時(shí),我們可以調(diào)用系統(tǒng)api,得到view的bitmap,但有時(shí)可能得不到。我們可以通過另一種方式得到。
首先創(chuàng)建一個(gè)和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);
執(zhí)行完上面代碼后bmp上就是view的影像了。
制造滾動(dòng)事件,促使view滾動(dòng)
我們可以創(chuàng)建一個(gè)MotionEvent,然后定時(shí)修改MotionEvent的y值,并分發(fā)給view,從而促使view上下滾動(dòng)。當(dāng)然我們也可以定時(shí)修改x值促使view左右滾動(dòng)。
代碼大致如下
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);
//把事件分發(fā)給view
view.dispatchTouchEvent(motionEvent);
view.postDelayed(this, DELAY);
}
}, DELAY);
注意:從分發(fā)DOWN事件到結(jié)束都要使用同一個(gè)MotionEvent對(duì)象,只需要不斷改變x或y值。
每次x或y的值相對(duì)于上次改動(dòng)不能過大,若過大,view實(shí)際滾動(dòng)距離可能達(dá)不到為MotionEvent設(shè)置的值(因view滾動(dòng)時(shí)卡頓導(dǎo)致)。
截屏
當(dāng)為MotionEvent設(shè)置的x或y值正好時(shí)當(dāng)前view的大小時(shí),創(chuàng)建新的bitmap,通過上述方法把view繪制到bitmap上,想要停止截屏?xí)r拼接所有bitmap即可。
備注
當(dāng)我們想要把Listview長(zhǎng)截屏?xí)r,需要為L(zhǎng)istView外面嵌套一層和ListView一樣大小的View,以上的所有操作都在嵌套的這層view上操作。當(dāng)我們調(diào)用嵌套的這層view的draw(new Canvas(bmp))時(shí)會(huì)把當(dāng)前看到的這塊ListView繪制到bmp上,不管ListView嵌套了多少層子view都可以繪制到當(dāng)前bmp上。
由于ListView中根據(jù)滑動(dòng)的距離是否大于ViewConfiguration.get(view.getContext()).getScaledTouchSlop() )來確定要不要滾動(dòng),所以一開始我們要特殊處理下,為什么是ViewConfiguration.get(view.getContext()).getScaledTouchSlop() )可以查看ListView的事件分發(fā)相關(guān)函數(shù)得到(dispatchTouchEvent),讓Listview認(rèn)為是開始滾動(dòng),這樣才能保證以后分發(fā)的滑動(dòng)距離和實(shí)際滾動(dòng)距離一致。
Listview也要通知是否滾動(dòng)到了最后,不然如果沒有手動(dòng)停止的話,雖然還是在一直分發(fā)滾動(dòng)事件,但ListView不再滾動(dòng),導(dǎo)致最終截圖后后面全是重復(fù)的最后一屏幕。
附 實(shí)現(xiàn)大致方式代碼,有待優(yōu)化
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);
//滑動(dòng)距離大于ViewConfiguration.get(view.getContext()).getScaledTouchSlop()時(shí)listview才開始滾動(dòng)
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) {
//停止時(shí)正好一屏則全部繪制,否則繪制部分
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);
//模擬每次向上滑動(dòng)一個(gè)像素,這樣可能導(dǎo)致滾動(dòng)特別慢,實(shí)際使用時(shí)可以修改該值,但判斷是否正好滾動(dòng)了
//一屏幕就不能簡(jiǎn)單的根據(jù) (view.getHeight() / 2 - (int) motionEvent.getY()) % view.getHeight() == 0 來確定了。
//可以每次滾動(dòng)n個(gè)像素,當(dāng)發(fā)現(xiàn)下次再滾動(dòng)n像素時(shí)就超出一屏幕時(shí)可以改變n的值,保證下次滾動(dòng)后正好是一屏幕,
//這樣就可以根據(jù)(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>
效果圖
屏幕
最終截屏
可以看到毫無拼接痕跡。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實(shí)現(xiàn)簡(jiǎn)易版打地鼠
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)易版打地鼠,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05
Android基于HttpUrlConnection類的文件下載實(shí)例代碼
本文通過實(shí)例代碼給大家介紹了Android基于HttpUrlConnection類的文件下載功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-09-09
100 行代碼實(shí)現(xiàn)Flutter自定義TabBar的示例代碼
這篇文章主要介紹了100 行代碼實(shí)現(xiàn)Flutter自定義TabBar的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
monkeyrunner之電腦安裝驅(qū)動(dòng)(5)
這篇文章主要為大家詳細(xì)介紹了monkeyrunner之電腦安裝驅(qū)動(dòng)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
android時(shí)間選擇控件之TimePickerView使用方法詳解
這篇文章主要為大家詳細(xì)介紹了android時(shí)間選擇控件之TimePickerView的使用方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09
Android開發(fā)實(shí)現(xiàn)跟隨手指的小球效果示例
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)跟隨手指的小球效果,涉及Android圖形繪制、事件響應(yīng)、界面布局等相關(guān)操作技巧,需要的朋友可以參考下2019-04-04
android 有阻尼下拉刷新列表的實(shí)現(xiàn)方法
下面小編就為大家分享一篇android 有阻尼下拉刷新列表的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,一起跟隨小編過來看看吧2018-01-01
Android 中對(duì)于圖片的內(nèi)存優(yōu)化方法
Android 中對(duì)于圖片的內(nèi)存優(yōu)化方法,需要的朋友可以參考一下2013-03-03

