ภาพรวม
บทความนี้สาธิตวิธี 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)
| Feature | Picasso | Glide | Coil (Kotlin) |
|---|---|---|---|
| Syntax | Simple, Chainable | Chainable, RequestOptions | Kotlin DSL, Coroutines |
| Resize/Crop | Built-in (.resize, .fit) | Built-in (.override, .centerCrop) | Built-in (.crossfade, .transform) |
| GIF/Animation | Limited | Excellent | Good |
| Video Thumbnail | No | Yes (MediaMetadataRetriever) | Yes |
| OkHttp Integration | Manual (Downloader) | Built-in | Built-in (OkHttp) |
| Size (Method Count) | ~800 | ~3000 | ~2000 (Kotlin only) |
| Lifecycle Awareness | Manual | RequestManager (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