31 ธันวาคม 2555

[Android Code] Image Viewer สำหรับแสดงภาพที่มีขนาดใหญ่


บทความนี้เจ้าของบล็อกขอคั่นด้วยเรื่องรูปภาพกันเสียหน่อย
เพราะดองไว้นานแล้วเหมือนกัน เลยเอาลงให้เสร็จๆไปเลย
คราวนี้ก็จะเป็นการสร้าง Image Viewer หรือหน้าแสดงภาพ
ซึ่งเอาไว้แสดงภาพขนาดใหญ่ๆ โดยสามารถซูมและเลื่อนได้

ถ้าผู้ที่หลงเข้ามาอ่านคนใดเคยเขียนแอพฯแสดงภาพใหญ่ๆ
หมายถึงภาพขนาดใหญ่ประมาณ 1000 x 1000 px ขึ้นไปนะ
ส่วนมากก็จะสร้าง Image View มาแสดงภาพตามปกติ
ถ้าไม่ได้เปลี่ยนภาพที่แสดงบ่อยๆก็ไม่มีปัญหาหรอก
ถ้าลองสมมติว่า บางคนทำปุ่มกดให้เปลี่ยนภาพที่แสดง
เมื่อกดเปลี่ยนภาพไปเรื่อยๆ ซักพักนึงแอพฯก็จะเด้งออก

(ใครที่ยังไม่รู้ลองเทสกับแอพฯของตัวเองดูได้นะ)

สำหรับปัญหานี้ถ้าไม่ลองใช้จริงมักจะไม่ค่อยรู้กันซักเท่าไร
เพราะว่าเวลาที่คนเขียนโปรแกรมทำการทดสอบแอพฯตัวเอง
ส่วนมากก็มักจะกดแค่นิดหน่อยเพื่อดูผลว่าโอเคหรือป่าว
โดยไม่ได้ทำการทดสอบการใช้งานจริงๆ จากผู้ใช้งาน
ซึ่งเจ้าของบล็อกแนะนำว่า การทดสอบแอพฯตัวเอง
ควรพยายามหาบั๊กให้ได้ด้วย ไม่ใช่แค่กดแล้วทำงานก็โอเค

กลับมาเข้าเรื่อง จากที่กดเปลี่ยนภาพไปเรื่อยๆ แอพฯก็จะเด้ง
ปัญหานี้คือ Out of memory ที่เกิดจากโหลดภาพขนาดใหญ่ๆ
เนื่องจากภาพที่มีขนาดใหญ่จะกิน Memory เยอะตามขนาด
เมื่อเปิดไปหลายๆภาพก็จะทำให้ Memory ไม่พอนั่นเอง

เจ้าของบล็อกก็มีวิธีแก้ปัญหาดังกล่าวให้เรียบร้อยแล้ว
แถมยังสามารถซูมหรือเลื่อนดูภาพนั้นๆได้อีกด้วย 
ซึ่งเจ้าของบล็อกขอเรียกว่า Image Viewer ละกันนะ
สำหรับคราวนี้ก็มาเป็นคลาสให้ใช้งานเหมือนเคย
แต่คลาสตัวนี้เจ้าของบล็อกไม่ได้เป็นคนเขียนขึ้นมาเอง
เป็นคลาสของคนอื่นที่เจ้าของบล็อกเอามาใช้งาน
(ถ้าจะให้เครดิตโค๊ดก็ให้เครดิตกับเจ้าของต้นฉบับไปเลยนะ)

โดยเจ้าของบล็อกได้แก้ไขโค๊ดเล็กน้อยเพื่อให้ใช้งานได้ง่าย
เพราะเดิมเป็นการดึงภาพจาก SD Card มาแสดงภาพ
แต่เจ้าของบล็อกจะให้ดึงภาพจากในแอพฯเลย
เลยเอามาแก้ไขให้ดึงภาพจากโฟลเดอร์ assets แทน
ดังนั้นในบทความนี้ก็จะให้เก็บไฟล์ภาพไว้ใน assets

ทีนี้ก็มาเข้าสู่การสร้าง Image Viewer กันเลยดีกว่า
ก่อนอื่นก็จะให้ดาวน์โหลดไฟล์ Library ไปไว้ใน Eclipse
โดยสามารถดาวน์โหลดได้ที่ ImageViewTouch [Google Drive]

เมื่อดาวน์โหลดแล้ว Extract ออกมาก็จะได้เป็นสองโฟลเดอร์
คือ ImageViewTouch ที่เป็น Library สำหรับ ImageViewer
กับ ImageViewTouchSample ที่เป็นตัวอย่างจากเจ้าของบล็อก
ให้ทำการ Import ทั้งสองโฟลเดอร์เข้า Eclipse ได้เลย



สำหรับทั้งสองโปรเจคนี้จะต้องใช้ Android 2.2 
หรือจะเปลี่ยนให้เป็น Anroid เวอร์ชันที่มีอยู่เลยก็ได้
แค่อย่าเป็นเวอร์ชันที่ต่ำกว่า Android 2.2 ก็พอ



เมื่อ Import เข้ามาเรียบร้อยแล้ว จะลองทดสอบการทำงาน
จาก ImageViewTouchSample เลยก็ได้ โดยติดตั้งลงเครื่องได้เลย
แต่ถ้าผู้ที่หลงเข้ามาอ่านคนใดจะเอาไปใช้งานก็ดูขั้นตอนต่อไปเลย

ทีนี้ให้สร้างโปรเจคใหม่ขึ้นมาเลย ตั้งชื่อและกำหนดโปรเจคได้ตามใจชอบ



เมื่อสร้างโปรเจคขึ้นมาใหม่แล้ว ยังไม่ต้องทำอะไร
ให้ไปที่โปรเจค ImageViewTouchSample โดยไปที่โฟลเดอร์ src
จะเห็นว่านอกจาก com.example.imageviewtouchsample แล้ว
ยังมี it.sephiroth.android.library.imagezoom.test.utils อยู่ด้วย
ซึ่งถ้าจะใช้ ImageViewTouch ก็จะต้องใช้ Package นี้ด้วย
แต่ไม่ต้องไปเขียนเองนะ เจ้าของบล็อกจะให้ก๊อปไปวางเลย

คลิกขวาที่ it.sephiroth.android.library.imagezoom.test.utils แล้วเลือก Copy




ทีนี้คลิกขวาที่โฟลเดอร์ src ของโปรเจคที่ได้สร้างขึ้นมาแล้วเลือก Paste




เพียงเท่านี้  it.sephiroth.android.library.imagezoom.test.utils
ก็จะมาอยู่ในโปรเจคที่ได้สร้างขึ้นมาเรียบร้อยแล้ว
(จริงๆจะใช้ CTRL + C แล้ว CTRL + V เลยก็ได้นะ ไวกว่าเยอะ)



ต่อมาก็ต้องกำหนด Library ของ ImageViewTouch ให้กับโปรเจคนี้ก่อน
โดยคลิกขวาที่โปรเจคที่ได้สร้างขึ้นมา แล้วเลือก Properties



ก็จะมีหน้าต่าง Properties แสดงขึ้นมา เลือกแท็บ Android
ฝั่งขวามือให้เลื่อนลงมาข้างล่างสุดจะเจอช่อง Library ให้กดปุ่ม Add



จะมีหน้าต่าง Project Selection แสดงขึ้นมา
ให้เลือก ImageViewTouch แล้วกด OK 



หน้าต่าง Properties ก็จะมี ImageViewTouch แสดงใน Library แล้ว
ก็ให้กด OK เพื่อปิดหน้าต่าง Properties ได้เลย เป็นอันเสร็จ




ทีนี้ก็หาภาพขนาดใหญ่ๆที่ต้องการมาซักหนึ่งภาพ
แล้วนำมาเก็บไว้ที่โฟลเดอร์ assets แทนที่จะเป็น drawable
เจ้าของบล็อกใช้ภาพ MyImage.jpg เป็นภาพขนาด 1920 x 1152 px



เมื่อเตรียมภาพและ Library แล้ว ก็เริ่มเขียนโปรแกรมต่อได้เลย

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"
    android:background="#000000" >

    <it.sephiroth.android.library.imagezoom.ImageViewTouch
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

สำหรับหน้า main.xml จะเห็นว่ามีการสร้าง Widget ที่เป็น
it.sephiroth.android.library.imagezoom.ImageViewTouch
ซึ่งเป็น Widget ที่มาจากคลาส ImageViewTouch นั่นเอง
โดยผู้เขียนคลาสนี้ได้สร้างขึ้นมาเอาไว้ใช้กับ ImageViewTouch
ส่วนการกำหนดค่าก็ไม่มีอะไรมาก กำหนดชื่อ ID เป็น image
และกำหนดความกว้างกับความสูงของ Layout เป็น match_parent


Main.java
package app.akexorcist.imageviewer;

import it.sephiroth.android.library.imagezoom.ImageViewTouch;
import it.sephiroth.android.library.imagezoom.test.utils.DecodeUtils;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.view.Menu;

public class Main extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        ImageViewTouch mImage = (ImageViewTouch)findViewById(R.id.image);
        Bitmap bitmap = DecodeUtils.decode(this
                , "MyImage.jpg", 2048, 2048);
        mImage.setImageBitmap(bitmap);
    }
}

ทีนี้ก็มาที่ Main.java มั่ง จะเห็นว่าโค๊ดมีแค่สามบรรทัด
บรรทัดแรกเป็นการประกาศ ImageViewTouch ขึ้นมา

ImageViewTouch mImage = (ImageViewTouch)findViewById(R.id.image);

โดยเจ้าของบล็อกตั้งชื่อของ Object เป็น mImage

ต่อมาเป็นคำสั่งดึงภาพใน assets แล้วแปลงเป็น Bitmap 

Bitmap bitmap = DecodeUtils.decode(this , "MyImage.jpg", 2048, 2048);


โดยนำภาพ MyImage.jpg ที่อยู่ใน assets มาแปลง 
ซึ่งเจ้าของบล็อกแก้ไขคำสั่งจากต้นฉบับตรงนี้
เพราะเดิมจะให้กำหนดไดเรคทอรี่ของไฟล์ใน SD Card
เลยแก้ไขให้พิมชื่อไฟล์ให้ตรงกับใน assets แล้วใช้ได้เลย
กรณีที่สร้างโฟลเดอร์แยกย่อยไว้ใน assets อีกทีหนึ่ง
อย่างเช่น assets/image/MyImage.jpg เวลาที่จะใช้งาน
ให้พิมเป็น "image/MyImage.jpg" เพื่อระบุโฟลเดอร์ด้วย
สำหรับ 2048 ที่กำหนดในคำสั่งจะเห็นว่ากำหนดไว้สองค่า
2048 แรกคือความกว้าง Bitmap สูงสุดของภาพหลังจากแปลง
2048 ถัดมาคือความสูง Bitmap สูงสุดของภาพหลังจากแปลง
ในกรณีที่ภาพที่นำมาแสดงมีขนาดไม่เกินอยู่แล้วก็ไม่เป็นไร
สมมติว่าภาพมีขนาด 3600 x 1400 เวลาแปลงเป็น Bitmap
ภาพนั้นๆ ก็จะถูกย่อให้มีขนาดเหลือแค่ 2048 x 796 
และสมมติว่าภาพมีขนาด 2592 x 4608 ก็จะถูกย่อเหลือ
1152 x 2048 (ก็เหมือนการประกาศขนาด Buffer นั่นแหละ)
การกำหนดค่านี้จะต้องไม่เกิน 2048 หรือจะน้อยกว่าก็ได้
ถ้าเกินนั้น ระบบก็จะมีเออเรอว่าจองพื้นที่ Buffer ใหญ่เกิน
เมื่อภาพถูกแปลงแล้วก็เก็บไว้ใน object ที่ชื่อว่า bitmap

เมื่อแปลงเป็น bitmap แล้วก็นำมากำหนดให้ mImage ได้เลย 
ด้วยคำสั่ง setImageBitmap (เหมือน ImageView นั่นแหละ)

mImage.setImageBitmap(bitmap);


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

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

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="app.akexorcist.imageviewer.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>

ทีนี้ก็ลองทดสอบดู พอเข้าแอพฯมาปุป ก็จะพบกับภาพที่ได้กำหนดไว้ทันที
โดยสามารถ Pinch Zoom เพื่อซูมดูภาพได้และ Drag เพื่อเลื่อนตำแหน่งภาพได้





ถ้าจะทำให้สามารถเลือกภาพที่จะแสดงได้ ให้ดูตัวอย่าง
จากโปรเจค ImageViewTouchSample ที่ได้ Import ไป
เพราะตัวอย่างนั้นจะมีสี่ภาพให้เลือก ว่าจะดูภาพไหน
ลองกดสลับภาพเรื่อยๆเทียบกับที่ใช้ Image View แสดงโดยตรง
ก็จะไม่พบปัญหา Out of memory แบบ Image View ต่อไปแล้ว 
(ถ้าลองของด้วยภาพขนาดใหญ่มากๆจะเห็นผลดีนักแล)

สำหรับผู้ที่หลงเข้ามาอ่านคนใดที่ต้องการไฟล์ตัวอย่าง
สามารถดาวน์โหลดได้จาก ImageViewer [Google Drive]

และสำหรับ Library และตัวอย่างจากเจ้าของบล็อก
สามารถดาวน์โหลดได้ที่ ImageViewTouch [Google Drive]




เหล่าพันธมิตรแอนดรอยด์

Devahoy Layer Net NuuNeoI The Cheese Factory Somkiat CC Mart Routine Artit-K Arnondora Kamonway Try to be android developer Oatrice Benz Nest Studios Kotchaphan@Medium Jirawatee@Medium Travispea@Medium