24 July 2014

การเซฟภาพหน้าจอแบบมี Dialog แสดงอยู่ด้วย

Updated on

        สำหรับบทความนี้ขอต่อเนื่องจากบทความเดิมเสียหน่อย [Android Code] การเซฟภาพหน้าจอแบบทั้งหน้าหรือบางส่วนที่ต้องการ ซึ่งเป็นการแคปภาพหน้าจอในแอปพลิเคชันของผู้ที่หลงเข้ามาอ่าน


        แต่ทว่าถ้าต้องการเซฟภาพที่เป็น Dialog ล่ะ? จะต้องทำยังไง? เพราะว่าคำสั่งจากบทความดังกล่าวนั้นไม่สามารถทำได้เลย ดังนั้นจึงเป็นที่มาของบทความนี้ที่จะเพิ่มเติมในส่วนที่ว่านี้

        จุดสำคัญนั้นอยู่ที่การดึง View จาก Dialog มานั่นเอง ซึ่งจะมีคำสั่งดังนี้
Dialog dialog = .....

...

View dialogView = dialog.getWindow().getDecorView();
dialogView.setDrawingCacheEnabled(true);
Bitmap bm = Bitmap.createBitmap(dialogView.getDrawingCache());
dialogView.setDrawingCacheEnabled(false);
        เท่านี้ก็จะได้ bm ที่เป็น Bitmap สำหรับภาพจาก Dialog แล้ว


        ถ้าอยากได้ภาพทั้งหน้าจอและ Dialog ล่ะ?

        สาเหตุที่เจ้าของบล็อกทำบทความนี้นั้นมาจากผู้ที่หลงเข้ามาอ่านท่านนึงได้ถามมาว่าจะบันทึกภาพหน้าจอทั้งๆที่มี Dialog แสดงอยู่ด้วยอย่างไร และเจ้าของบล็อกก็เห็นว่าน่าสนใจดีและบทความอันเก่าก็ไม่สามารถทำได้

        สำหรับคำตอบของปัญหานี้ เจ้าของบล็อกใช้วิธีการเก็บภาพจากหน้าจอแอปพลิเคชันและ Dialog แยกกันเป็นสองส่วน (เพราะมันดันเก็บได้ทีละส่วนนี่สิ)  เมิ่อได้ Bitmap ของทั้งสองส่วนแล้วก็ค่อยนำมาซ้อนกันนั่นเอง เท่านี้ก็ได้ภาพหน้าจอแอปพลิเคชันที่มี Dialog อยู่ด้วยแล้ว~

Dialog dialog = .....

// Get bitmap from root layout
View view = findViewById(android.R.id.content).getRootView();
view.setDrawingCacheEnabled(true);
Bitmap bm1 = Bitmap.createBitmap(view.getDrawingCache());
view.setDrawingCacheEnabled(false);

// Get bitmap from dialog layout
View dialogView = dialog.getWindow().getDecorView();
dialogView.setDrawingCacheEnabled(true);
Bitmap bm2 = Bitmap.createBitmap(dialogView.getDrawingCache());
dialogView.setDrawingCacheEnabled(false);
                            
// Merge bitmap
Bitmap bitmap = Bitmap.createBitmap(bm1.getWidth()
        , bm1.getHeight(), bm1.getConfig());
Canvas c = new Canvas(bitmap);
c.drawBitmap(bm1, new Matrix(), null);
c.translate((bm1.getWidth() / 2) - (bm2.getWidth() / 2)
        , (bm1.getHeight() / 2) - (bm2.getHeight() / 2));
c.drawBitmap(bm2, new Matrix(), null);

// Clear bitmap
bm1.recycle();
bm2.recycle();
        เท่านี้ก็จะได้ bitmap ที่เป็น Bitmap สำหรับภาพหน้าจอแอปพลิเคชันรวมไปถึง Dialog แล้ว


        สำหรับตัวอย่างในบทความนี้ก็จะให้กดที่ภาพแล้วจะมี Dialog ขึ้นมาพร้อมกับมีปุ่ม Save ให้กดเพื่อบันทึกภาพ (ทำมาจาก Custom Dialog นั่นเอง สามารถดูเพิ่มเติมได้จาก [Android Code] การสร้าง Custom Dialog) เมื่อผู้ใช้กดก็จะทำการบันทึกภาพเก็บไว้ในเครื่อง โดยจะอยู่ในโฟลเดอร์ Pictures ของเครื่อง ส่วนชื่อไฟล์ภาพที่บันทึกก็จะอิงจากเวลาและวันที่ ณ ตอนที่กดบันทึกภาพ

Main.java
package app.akexorcist.screenshotdialog;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;

import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.text.format.DateFormat;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

public class Main extends Activity {
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        ImageView imgDialog = (ImageView)findViewById(R.id.imgDialog);
        imgDialog.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                final Dialog dialog = new Dialog(Main.this);
                dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
                dialog.setContentView(R.layout.customdialog);
                dialog.setCancelable(true);
                
                Button btnSave = (Button)dialog.findViewById(R.id.btnSave);
                btnSave.setOnClickListener(new OnClickListener() {
                    public void onClick(View v) {
                        saveScreen(dialog);
                    }
                });
                
                dialog.show();
            }
        });
    }
    
    public void saveScreen(Dialog dialog) {
        // Get bitmap from root layout
        View view = findViewById(android.R.id.content).getRootView();
        view.setDrawingCacheEnabled(true);
        Bitmap bm1 = Bitmap.createBitmap(view.getDrawingCache());
        view.setDrawingCacheEnabled(false);
        
        // Get bitmap from dialog layout
        View dialogView = dialog.getWindow().getDecorView();
        dialogView.setDrawingCacheEnabled(true);
        Bitmap bm2 = Bitmap.createBitmap(dialogView.getDrawingCache());
        dialogView.setDrawingCacheEnabled(false);
        
        // Merge bitmap
        Bitmap bitmap = Bitmap.createBitmap(bm1.getWidth()
                , bm1.getHeight(), bm1.getConfig());
        Canvas c = new Canvas(bitmap);
        c.drawBitmap(bm1, new Matrix(), null);
        c.translate((bm1.getWidth() / 2) - (bm2.getWidth() / 2)
                , (bm1.getHeight() / 2) - (bm2.getHeight() / 2));
        c.drawBitmap(bm2, new Matrix(), null);
        
        // Clear bitmap
        bm1.recycle();
        bm2.recycle();
        
        try {
            // Save bitmap to storage
            Date d = new Date();
            String filename  = (String)DateFormat.format("hhmmss-MMddyyyy"
                    , d.getTime());
            File dir = new File(Environment.getExternalStorageDirectory()
                    , "/Pictures/" + filename + ".jpg");
            FileOutputStream out = new FileOutputStream(dir);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
            out.write(bos.toByteArray());

            // Clear bitmap
            bitmap.recycle();
            
            // Update image to media system
            Intent intent = 
                    new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
            intent.setData(Uri.fromFile(dir));
            sendBroadcast(intent);
            
            Toast.makeText(getApplicationContext(), "Saved!"
                    , Toast.LENGTH_SHORT).show();
        } catch(FileNotFoundException e) {
            e.printStackTrace();
        } catch(IOException e) {
            e.printStackTrace();
        }
    }
}

        สำหรับในตัวอย่างเจ้าของบล็อกเพิ่มเข้าไปบางส่วน เพื่อให้รวมภาพทั้งสองเข้าด้วยกันแล้วทำการบันทึกภาพลงในเครื่อง แล้วทำการอัปเดตให้กับ Media Scanner เพื่อให้ภาพแสดงใน Gallery ทันที แล้วแสดงผ่าน Toast เพื่อให้รู้ว่าบันทึกภาพเรียบร้อยแล้ว และส่วนที่สำคัญก็คือการ Recycle เหล่า Bitmap ที่เก็บไว้ทั้งหลายครับ เมื่อ Bitmap ตัวไหนใช้เรียบร้อยแล้วก็ควรจะทำการ Recycle เพื่อเคลียร์หน่วยความจำซะ ไม่เช่นนั้นจะ Memory Leak


main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView
        android:id="@+id/imgDialog"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:scaleType="centerCrop"
        android:src="@drawable/profile" />

</RelativeLayout>


customdialog.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="30dp" >

    <ImageView
        android:layout_width="200dp"
        android:layout_height="60dp"
        android:scaleType="fitCenter"
        android:src="@drawable/photo" />

    <Button
        android:id="@+id/btnSave"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Save" />

</LinearLayout>


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="app.akexorcist.screenshotdialog"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".Main"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


        ดาวน์โหลดไฟล์ตัวอย่าง

                • Screenshot Dialog [GitHub]
                • Screenshot Dialog [Google Drive]
                • Screenshot Dialog [Sleeping For Less]