Android长截屏(滚动截屏)实现原理
Google原生Android系統到目前為止均沒有長截屏這一功能,而對于用戶而言,這是一個非常實用的功能,如聊天記錄,新聞頁面等較長的頁面想通過一張圖片的形式保存起來.好在國內主流手機廠商均已實現了該功能,接下來聊聊我們長截屏的實現原理.
長截屏原理概述:
我們簡單的把一個屏幕分成三分,上中下,中間區域最大,中間區域也就是滑動區域;長截屏開始,我們截取頂部的圖片保存到集合中,截取長度如下,然后每次滑動圖片前截取中間部分圖片保存到集合中,也就是滑動多少次就會有多少張中間區域圖片,最后保存時,截取底部圖片保存到集合中;隨后把集合中的圖片拼接起來就完成了長截屏
中間部分為滑動區域,也就是我們模擬手指在屏幕上滑動一個指定長度,這個長度也就是中間圖片的長度.模擬滑動代碼如下:
// 模擬Down事件MotionEvent tempDown = MotionEvent.obtain(downTime, eventTime, 0, toX, toY, 0);tempDown.setSource(4098);InputManager.getInstance().injectInputEvent(tempDown, Integer.valueOf(2));// 模擬Move事件MotionEvent tempMove = MotionEvent.obtain(downTime, eventTime, 2, toX, toY, 0);tempMove.setSource(4098);InputManager.getInstance().injectInputEvent(tempMove, Integer.valueOf(2));// 模擬Up事件MotionEvent tempUP = MotionEvent.obtain(downTime, eventTime, 1, toX, toY, 0);tempUP.setSource(4098);InputManager.getInstance().injectInputEvent(tempUP, Integer.valueOf(2));injectInputEvent方法為@hide,沒環境調用的可以通過反射調用:
try {Class<?> ClassInputManager = Class.forName("android.hardware.input.InputManager");Method[] methods = ClassInputManager.getMethods();Method methodGetInstance = null;Method methodInjectInputEvent = null;Method method = methods[0];for (int i = 0; i < methods.length; i++) {method = methods[i];if (method.getName().equals("getInstance")) {methodGetInstance = method;}if (method.getName().equals("injectInputEvent")) {methodInjectInputEvent = method;}}Object mInstance = methodGetInstance.invoke(ClassInputManager, new Object[0]);long downTime = SystemClock.uptimeMillis();long eventTime = SystemClock.uptimeMillis();float y = fromY;if (fromY > toY) {// action downMotionEvent eventDown = MotionEvent.obtain(downTime, eventTime, 0, fromX, y, 0);eventDown.setSource(4098);Object[] arrayOfObject2 = new Object[2];arrayOfObject2[0] = eventDown;arrayOfObject2[1] = Integer.valueOf(2);methodInjectInputEvent.invoke(mInstance, arrayOfObject2);eventDown.recycle();}// action moveMotionEvent eventMove2 = MotionEvent.obtain(downTime, eventTime, 2, toX, y, 0);eventMove2.setSource(4098);Object[] objMove2 = new Object[2];objMove2[0] = eventMove2;objMove2[1] = Integer.valueOf(2);methodInjectInputEvent.invoke(mInstance, objMove2);eventMove2.recycle();// action upMotionEvent eventUp = MotionEvent.obtain(downTime, eventTime, 1, toX, toY, 0);eventUp.setSource(4098);Object[] objUp = new Object[2];objUp[0] = eventUp;objUp[1] = Integer.valueOf(2);methodInjectInputEvent.invoke(mInstance, objUp);eventUp.recycle();} catch (Exception e) {e.printStackTrace();}細節(核心):
以上方式完成長截屏理論上沒有問題,但是還有一個細節,也是核心;比如每次模擬滑動10厘米,但是最后一次滑動已經接近底部了,實際距離不到10厘米,因此最后一張圖片的實際長度我們無法知道.(通俗的說是我們默認滑動10厘米,但是約2厘米的時候到底劃不動了,而默認截取的長度還是10厘米,因此我們需要去掉多余的8厘米長度圖片)
左邊為中間區域倒數第二張圖片 右邊為中間區域倒數第一張圖片:
如上圖所示,藍色矩形部分為最后一張圖片的實際滑動距離,紅叉區域由于滑動到底,滑不動導致的重疊部分.
因此,我們需要截取最后一張圖的實際長度,如下圖:
怎么樣去掉重疊部分的圖片?
如下圖,截取上述兩張圖片的左側區域(2號圖的左下紅色區域,1號圖的左側紅色區域),接下進行圖片匹配裁剪,排除部分滑動控件有SeekBar的情況.
如上圖,獲取裁減后的圖2,從底部開始,取一個像素高的圖片,所得圖片的每個像素顏色轉為int值,存入pixels2,函數如下:
target.getPixels(pixels2, 0, this.mCropW, 0, (target.getHeight() - 1), this.mCropW, 1);
從底部開始依次遍歷圖1一個像素為高度的圖片,同理獲取所得圖片的每個像素顏色轉為int值,存入pixels1;如果匹配,將匹配的圖片高度存入lineList集合
for (int i = 0; i < source.getHeight(); i = i + 1) {source.getPixels(pixels1, 0, this.mCropW, 0, i, this.mCropW, 1);if (Arrays.equals(pixels2, pixels1)) {lineList.add(Integer.valueOf(i));}}由于我們取的是一個像素為高度的樣本圖片,因此匹配到相等的情況比較常見.
隨后,遍歷lineList,對匹配相識度最高的圖片的高度進行裁剪.
匹配方法和上述方法類似,遍歷裁剪后的圖2的高度,遍歷累計增加一個高度;圖1裁剪后的圖片取底部一個像素的高度和圖2遍歷的高度取一個像素的高度對比,如果相等記錄一下,隨后依次取倒數第二個像素開始的一個像素的高度進行對比,通過總次數和匹配相等的次數算是一個匹配概率.最后取概率最高的lineList中的高度.
private float compareLastBitmap(Bitmap bitmap2, Bitmap bitmap1, int index) {int[] pixels_target = new int[mCropW];int[] pixels_source = new int[mCropW];int count = 0;int height = Math.min((bitmap2.getHeight() - 1), index);height = Math.max(height, 1);for (int i = 0x0; i < height; i = i + 1) {// 倒數第二張圖片的底部 一個像素高度的圖片 (遍歷累計減少高度)bitmap2.getPixels(pixels_target, 0, mCropW, 0, ((bitmap2.getHeight() - 1) - i), mCropW, 1);// 最后一張圖片 指定高度開始(遍歷累計減少高度) 一個像素高度的圖片bitmap1.getPixels(pixels_source, 0, mCropW, 0, (index - i), mCropW, 1);if (Arrays.equals(pixels_target, pixels_source)) {count = count + 1;}}return (float) count / (float) height;}// 取概率最高的lineList中的高度int line_index = -1;for (int i = 0; i < lineList.size(); i = i + 1) {float percent = compareLastBitmap(target, source, lineList.get(i).intValue());if (percent == 1.0) {list.add(lineList.get(i).intValue());if (list.size() > 80) { // 底部圖片相同導致size過大循環過長的情況line_index = lineList.get(i).intValue();list.clear();break;}}if (percent > bigger) {bigger = percent;line_index = lineList.get(i).intValue();}}貌似找到匹配度為100%然后對對應圖片高度進行裁剪就完了,但是匹配度為100%情況有時候會出現多個,這是為什么?
原來當一張圖片中 相似的部分很多的時候(例如圖片中重復空白區域很多),我們每次取一個像素進行對比很容易出現匹配度為100%的情況,因此接下我們還得為這種情況進行處理.
通過遞歸的方式每次累計增加樣本圖片的高度(之前每次都是取一個像素為高度,遞歸累計增加圖片的高度進行匹對)
// space為取對比圖片的高度為幾個像素private int compare(Bitmap target, Bitmap source, ArrayList<Integer> list, int space) {int count = 0;int line_height = 0;for (int j = 0; j < list.size(); j++) {int[] pixels_target = new int[mCropW * space];int[] pixels_source = new int[mCropW * space];line_height = list.get(j).intValue();if ((line_height - space) < 0) {break;}if ((target.getHeight() - space) < 0) {break;}// 取space高度的樣本對兩張圖片進行比較,遍歷累計增加樣本圖片的高度target.getPixels(pixels_target, 0, this.mCropW, 0, (target.getHeight() - space), this.mCropW, space);source.getPixels(pixels_source, 0, this.mCropW, 0, line_height - space, this.mCropW, space);if (Arrays.equals(pixels_target, pixels_source)) {count++;}}// 遞歸增加圖片高度進行對比if (count > 1) {return compare(target, source, list, ++space);}return line_height;}獲取需要裁剪的高度后,裁剪圖片,裁剪后的圖片如下:
隨后將如上圖片替換到圖片集合中最后一張圖片(有重復區域的圖片).
最后將如下底部的圖片保存到集合,然后進行圖片拼接,完成長截屏.
圖片拼接: 首先創建一個 所有集合中圖片長之和為長,屏寬為寬度的圖片;遍歷集合,依次將集合中的圖片按坐標繪制到剛剛所創建的圖片上
Android屏幕截圖實現方式 & 系統截屏源碼分析和三指截屏
Android屏幕錄制源碼Demo下載
?
總結
以上是生活随笔為你收集整理的Android长截屏(滚动截屏)实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: IDEA启动tomcat报错java.u
- 下一篇: jdk1.6 改 jdk1.7或jdk1
