Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธี Resize รูปภาพด้วย Picasso ใน Android

ภาพรวม

บทความนี้สาธิตวิธี Resize (ปรับขนาด) รูปภาพใน Android โดยใช้ Library **Picasso** ของ Square ซึ่งเป็น Image Loading Library ที่นิยม ใช้งานง่าย รองการ Resize, Crop, Transform, Placeholder, Error Handling และ Caching อัตโนมัติ

ขั้นตอนที่ 1: สร้างโปรเจกต์และเพิ่ม Dependency

ใน build.gradle (Module: app):

dependencies {
    // Picasso Latest Version
    implementation "com.squareup.picasso:picasso:2.8"
    
    // Optional: OkHttp3 Downloader (แนะนำสำหรับ Network)
    implementation "com.squareup.okhttp3:okhttp:4.11.0"
    // implementation "com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0"
}

ขั้นตอนที่ 2: Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    android:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Picasso Resize Demo"
        android:textSize="20sp"
        android:textStyle="bold"
        android:layout_marginBottom="16dp" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:scaleType="centerCrop"
        android:background="#f0f0f0"
        android:contentDescription="Loaded Image" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:orientation="horizontal"
        android:gravity="center">

        <Button
            android:id="@+id/btnResizeFixed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Resize 200x500" />

        <Button
            android:id="@+id/btnResizeFit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:text="Fit Center" />

        <Button
            android:id="@+id/btnResizeCrop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:text="Center Crop" />

    </LinearLayout>

</LinearLayout>

ขั้นตอนที่ 3: MainActivity.java - Picasso Resize Techniques

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import com.squareup.picasso.Callback;
import com.squareup.picasso.MemoryPolicy;
import com.squareup.picasso.NetworkPolicy;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Transformation;

public class MainActivity extends AppCompatActivity {

    private ImageView imageView;
    private Button btnResizeFixed, btnResizeFit, btnResizeCrop;
    
    // URL รูปภาพทดสอบ (Pexels - Free Stock Photos)
    private static final String IMAGE_URL = "https://images.pexels.com/photos/414612/pexels-photo-414612.jpeg";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageView = findViewById(R.id.imageView);
        btnResizeFixed = findViewById(R.id.btnResizeFixed);
        btnResizeFit = findViewById(R.id.btnResizeFit);
        btnResizeCrop = findViewById(R.id.btnResizeCrop);

        // Default Load
        loadWithResize(200, 500, null);

        btnResizeFixed.setOnClickListener(v -> loadWithResize(200, 500, null));
        btnResizeFit.setOnClickListener(v -> loadWithFit());
        btnResizeCrop.setOnClickListener(v -> loadWithCenterCrop());
    }

    // 1. Resize คงที่ (Fixed Size) - บีบ/ยืดไม่สน Aspect Ratio
    private void loadWithResize(int width, int height, Transformation transformation) {
        Picasso.get()
            .load(IMAGE_URL)
            .resize(width, height) // กำหนดขนาดพิกเซลแน่นอน
            .centerInside() // หรือ centerCrop() - ต้องเรียกหลัง resize
            .placeholder(R.drawable.placeholder) // รอโหลด
            .error(R.drawable.error_image) // Error
            .into(imageView, new Callback() {
                @Override public void onSuccess() {
                    Toast.makeText(MainActivity.this, "Loaded: " + width + "x" + height, Toast.LENGTH_SHORT).show();
                }
                @Override public void onError(Exception e) {
                    Toast.makeText(MainActivity.this, "Error: " + e.getMessage(), Toast.LENGTH_SHORT).show();
                }
            });
    }

    // 2. Fit Center - รักษา Aspect Ratio ให้พอดีในกรอบ
    private void loadWithFit() {
        Picasso.get()
            .load(IMAGE_URL)
            .fit() // ปรับขนาดให้พอดี ImageView โดยอัตโนมัติ (ต้องมีขนาด ImageView แน่นอน)
            .centerInside() // หรือ centerCrop()
            .placeholder(R.drawable.placeholder)
            .error(R.drawable.error_image)
            .into(imageView, new Callback() {
                @Override public void onSuccess() {
                    Toast.makeText(MainActivity.this, "Fit Center Loaded", Toast.LENGTH_SHORT).show();
                }
                @Override public void onError(Exception e) {
                    Toast.makeText(MainActivity.this, "Error: " + e.getMessage(), Toast.LENGTH_SHORT).show();
                }
            });
    }

    // 3. Center Crop - รักษา Aspect Ratio แต่ Crop ให้เต็มกรอบ
    private void loadWithCenterCrop() {
        Picasso.get()
            .load(IMAGE_URL)
            .resize(400, 400) // Resize ก่อน Crop
            .centerCrop() // Crop กลางให้เต็ม 400x400
            .placeholder(R.drawable.placeholder)
            .error(R.drawable.error_image)
            .into(imageView);
    }

    // ==================== Advanced Techniques ====================

    // 4. Resize ด้วย Target (Custom Target) - สำหรับได้ Bitmap โดยตรง
    private void loadIntoTarget() {
        com.squareup.picasso.Target target = new com.squareup.picasso.Target() {
            @Override
            public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
                // ได้ Bitmap โดยตรง - ทำอะไรก็ได้: Save, Share, Process, Set to other View
                imageView.setImageBitmap(bitmap);
            }

            @Override
            public void onBitmapFailed(Exception e, android.graphics.drawable.Drawable errorDrawable) {
                Toast.makeText(MainActivity.this, "Failed: " + e.getMessage(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onPrepareLoad(android.graphics.drawable.Drawable placeholderDrawable) {
                imageView.setImageDrawable(placeholderDrawable);
            }
        };
        
        // Important: Keep Strong Reference to Target ไม่งั้น GC จะเก็บไป
        // imageView.setTag(target); 
        
        Picasso.get()
            .load(IMAGE_URL)
            .resize(300, 300)
            .centerCrop()
            .into(target);
    }

    // 5. Custom Transformation (เช่น Rounded Corners, Blur, Circle)
    private void loadWithCustomTransform() {
        Picasso.get()
            .load(IMAGE_URL)
            .resize(300, 300)
            .centerCrop()
            .transform(new RoundedCornersTransformation(24, 0)) // Radius 24dp
            .into(imageView);
    }

    // Custom Transformation Class
    public static class RoundedCornersTransformation implements Transformation {
        private final int radius;
        private final int margin;

        public RoundedCornersTransformation(int radius, int margin) {
            this.radius = radius;
            this.margin = margin;
        }

        @Override
        public Bitmap transform(Bitmap source) {
            Bitmap output = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
            android.graphics.Canvas canvas = new android.graphics.Canvas(output);
            android.graphics.Paint paint = new android.graphics.Paint();
            paint.setAntiAlias(true);
            android.graphics.RectF rect = new android.graphics.RectF(margin, margin, source.getWidth() - margin, source.getHeight() - margin);
            canvas.drawRoundRect(rect, radius, radius, paint);
            paint.setXfermode(new android.graphics.PorterDuffXfermode(android.graphics.PorterDuff.Mode.SRC_IN));
            canvas.drawBitmap(source, 0, 0, paint);
            if (source != output) source.recycle();
            return output;
        }

        @Override
        public String key() {
            return "rounded_corners_" + radius;
        }
    }

    // 6. Memory/Network Policy
    private void loadWithPolicies() {
        Picasso.get()
            .load(IMAGE_URL)
            .resize(200, 200)
            .centerCrop()
            .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE) // Skip Memory Cache
            .networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE) // Skip Disk Cache
            .into(imageView);
    }

    // 7. Priority (High/Low)
    private void loadWithPriority() {
        Picasso.get()
            .load(IMAGE_URL)
            .resize(200, 200)
            .centerCrop()
            .priority(Picasso.Priority.HIGH) // โหลดก่อน Low Priority
            .into(imageView);
    }

    // 8. Tag และ Cancel Request
    private void loadWithTag() {
        Picasso.get()
            .load(IMAGE_URL)
            .resize(200, 200)
            .centerCrop()
            .tag("myTag") // Tag สำหรับ Cancel
            .into(imageView);
    }

    private void cancelRequests() {
        Picasso.get().cancelTag("myTag"); // Cancel ทุก Request ที่มี Tag นี้
        // หรือ Picasso.get().cancelRequest(imageView); // Cancel สำหรับ View นี้
    }
}

ขั้นตอนที่ 4: AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

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

Picasso Resize Methods เปรียบเทียบ

MethodพฤติกรรมAspect Ratioใช้เมื่อ
.resize(w, h)บังคับขนาดพิกเซลแน่นอนไม่รักษา (ยืด/บีบ)Thumbnail ขนาดคงที่, Placeholder
.resize(w, h).centerInside()Resize แล้ว Fit ภายในรักษาแสดงรูปเต็มในกรอบ
.resize(w, h).centerCrop()Resize แล้ว Crop กลางรักษา (Crop)Grid, Cover Image, Profile
.fit()ปรับขนาดตาม ImageViewรักษาImageView มีขนาดคงที่ (match_parent, dp)
.fit().centerCrop()Fit แล้ว Crop กลางรักษา (Crop)Background, Hero Image

Picasso vs Glide vs Coil (2024)

FeaturePicassoGlideCoil (Kotlin)
SyntaxSimple, ChainableChainable, RequestOptionsKotlin DSL, Coroutines
Resize/CropBuilt-in (.resize, .fit)Built-in (.override, .centerCrop)Built-in (.crossfade, .transform)
GIF/AnimationLimitedExcellentGood
Video ThumbnailNoYes (MediaMetadataRetriever)Yes
OkHttp IntegrationManual (Downloader)Built-inBuilt-in (OkHttp)
Size (Method Count)~800~3000~2000 (Kotlin only)
Lifecycle AwarenessManualRequestManager (Auto)Coroutine Scope (Auto)

Best Practices

  • Placeholder/Error: ตั้งค่าทุกครั้ง ป้องกัน Layout Shift
  • Resize ก่อน Load: .resize() ลด Memory Usage และ Bandwidth (Server-side resize ดีกว่า)
  • fit() ต้องการ View Size: ใช้ได้ก็ต่อเมื่อ ImageView มีขนาดแน่นอน (ไม่ใช่ wrap_content)
  • Target Reference: ถ้าใช้ Target ต้อง Keep Reference ไว้ (setTag หรือ Field)
  • List/RecyclerView: Picasso จัดการ Cancel/Reuse อัตโนมัติผ่าน Tag
  • Disk Cache: Picasso Cache อัตโนมัติ (2% Disk Space) - Config ได้ผ่าน OkHttp Cache