Android實現(xiàn)壓縮本地圖片功能
1. 項目概述
在各類社交、商城、相冊等應用中,圖片常常需要經過壓縮處理以降低文件體積、縮減上傳流量和減少內存占用。圖片壓縮不僅能夠提高傳輸效率,還能在保證一定質量的前提下改善用戶體驗。
本項目旨在通過 Android 平臺實現(xiàn)本地圖片壓縮功能,主要目標包括:
從本地選擇圖片:借助系統(tǒng)相冊或文件選擇器,讓用戶選擇需要壓縮的圖片;
Bitmap 壓縮處理:利用 BitmapFactory.Options 控制采樣率,通過 Bitmap.compress() 方法實現(xiàn)質量壓縮;
圖片預覽與存儲:在壓縮后將圖片顯示在界面上,并支持將結果保存到本地;
性能與資源管理:優(yōu)化大圖片處理和內存使用,避免內存溢出和 ANR;
模塊化設計:所有代碼均整合在一起,通過詳細注釋分模塊說明,便于后續(xù)擴展。
2. 背景與相關技術解析
2.1 圖片壓縮的意義與應用場景
節(jié)省網絡流量:壓縮后的圖片體積更小,上傳下載速度更快,減少用戶流量消耗。
降低服務器壓力:上傳圖片體積減小可以節(jié)省服務器存儲空間和提高處理效率。
改善內存使用:在展示大尺寸圖片時,通過采樣降低內存占用,避免內存溢出問題。
提升用戶體驗:圖片加載快、展示流暢對于用戶體驗至關重要,尤其在低網速環(huán)境下尤為明顯。
2.2 Android 圖片處理基礎與 Bitmap 概述
Bitmap 類:在 Android 中,Bitmap 是代表圖像數據的核心類。了解 Bitmap 的內存占用、分辨率、色彩配置(如 ARGB_8888 與 RGB_565)等是圖片處理的基礎。
BitmapFactory:提供從文件、資源、輸入流中加載 Bitmap 的方法,支持通過 Options 設置采樣率(inSampleSize)降低圖片分辨率。
Canvas 與 Paint:在對 Bitmap 進行加工時,通常會使用 Canvas 和 Paint 實現(xiàn)圖片繪制和合成。
2.3 BitmapFactory.Options 與 inSampleSize 的作用
inSampleSize 參數:這是 BitmapFactory.Options 中的重要參數,通過設置 inSampleSize 的值可以按比例縮小原始圖片的尺寸。例如,設置 inSampleSize = 2 將圖片寬高減半,占用內存減少為原來的四分之一。
質量與性能平衡:合理設置 inSampleSize 能夠在保證圖片質量的前提下,降低內存消耗和處理時間,適合大圖上傳場景。
2.4 Bitmap.compress() 方法及圖片編碼
Bitmap.compress() 方法:用于將 Bitmap 壓縮為指定格式(如 JPEG、PNG、WEBP)的數據流。通過設置壓縮質量(0-100),在保證圖像清晰度和文件體積之間達到平衡。
壓縮格式選擇:JPEG 格式適用于彩色照片,壓縮后體積較小,但可能出現(xiàn)失真;PNG 格式無損但文件體積較大;WEBP 格式兼具兩者優(yōu)勢,但僅在部分 Android 版本支持。
3. 項目需求與實現(xiàn)難點
3.1 項目需求說明
本項目主要需求如下:
選擇本地圖片
利用系統(tǒng)相冊或文件選擇器,讓用戶選擇需要上傳的圖片,并在界面上顯示預覽。
圖片壓縮
通過 BitmapFactory.Options 控制 inSampleSize 進行采樣壓縮;
通過 Bitmap.compress() 對 Bitmap 進行質量壓縮,支持 JPEG、PNG 等格式,用戶可以選擇壓縮質量參數。
結果展示與保存
將壓縮后的圖片顯示在界面 ImageView 上,并支持保存壓縮圖片到本地存儲。
后臺異步操作
網絡傳輸與圖片處理操作必須在后臺線程中完成,避免阻塞 UI 主線程。
異常與權限管理
處理內存溢出、 IO 異常等問題;在 AndroidManifest.xml 中聲明必要的權限,如 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE。
代碼結構與擴展性
所有 Java 與 XML 代碼均整合在一起,通過詳細注釋區(qū)分不同模塊,保證代碼清晰、便于維護和后續(xù)擴展(例如批量壓縮、在線上傳結合等)。
3.2 實現(xiàn)難點與挑戰(zhàn)
主要難點與挑戰(zhàn)包括:
大圖內存管理
加載高清大圖時很容易出現(xiàn)內存溢出,需合理采樣和使用 inSampleSize 降低 Bitmap 尺寸。壓縮質量與文件體積平衡
如何在保證圖片關鍵細節(jié)不失真的前提下,盡可能降低圖片文件體積,需要設置合適的壓縮格式和質量參數。異步與多線程處理
圖片壓縮和保存操作耗時較長,必須在后臺線程異步處理,同時確保 UI 能正確更新處理結果。保存與分享機制
壓縮完成后將圖片保存到本地或提供分享功能,需要處理文件讀寫、路徑管理與兼容性問題。
4. 設計思路與整體架構
4.1 總體設計思路
本項目采用基于 Bitmap 操作和 Canvas 繪制的方式實現(xiàn)圖片壓縮,主要設計思路如下:
圖片選擇與加載
通過系統(tǒng)相冊選擇器(Intent.ACTION_PICK)獲取用戶選擇的圖片 URI,利用 BitmapFactory.Options 按需設置 inSampleSize 進行采樣加載 Bitmap,降低內存占用。圖片壓縮模塊
編寫 ImageCompressionUtils 工具類,提供兩種壓縮方法:利用 inSampleSize 調整分辨率,達到采樣壓縮效果;
調用 Bitmap.compress() 方法對 Bitmap 進行質量壓縮,支持 JPEG、PNG、WEBP 格式,并設置壓縮質量參數。
保存與分享模塊
將壓縮后的 Bitmap 保存到本地文件系統(tǒng),利用 FileOutputStream 寫入數據,同時可以通過 Intent 分享該圖片。UI 與交互模塊
主 Activity 提供圖片預覽、壓縮參數調整(例如壓縮比例、質量)和上傳/分享按鈕,實時顯示壓縮效果反饋給用戶。后臺與異步操作
圖片壓縮及文件保存操作使用 AsyncTask(或其他異步編程方式)在后臺線程執(zhí)行,確保 UI 順暢。
4.2 模塊劃分與邏輯結構
項目主要模塊分為以下幾部分:
圖片選擇與加載模塊
通過系統(tǒng)相冊調用,讓用戶選擇圖片,并使用 BitmapFactory.Options 加載原始 Bitmap。
圖片壓縮工具模塊(ImageCompressionUtils.java)
提供靜態(tài)方法 compressImageBySampling() 和 compressImageByQuality(),分別實現(xiàn)采樣壓縮與質量壓縮功能。
保存與分享模塊
提供保存 Bitmap 到文件的方法,并支持調用系統(tǒng)分享功能。
UI 與活動模塊
MainActivity 展示圖片預覽、壓縮參數設置、壓縮執(zhí)行和結果展示,并調用圖片壓縮工具模塊。
布局與資源管理模塊
整合所有 XML 布局文件(主 Activity 布局、按鈕與預覽區(qū)域)、顏色、字符串和樣式資源,通過詳細注釋區(qū)分各部分。
5. 完整代碼實現(xiàn)
下面提供完整代碼示例,其中所有 Java 與 XML 代碼均整合在一起,并通過詳細注釋分隔不同模塊。本示例中采用 ImageCompressionUtils 工具類實現(xiàn)圖片壓縮功能,MainActivity 負責圖片選擇、顯示和調用壓縮方法。
5.1 Java 代碼實現(xiàn)
// ===========================================
// 文件: ImageCompressionUtils.java
// 描述: 圖片壓縮工具類,提供基于采樣率與質量壓縮兩種方法
// ===========================================
package com.example.uploaddemo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
public class ImageCompressionUtils {
/**
* 利用 BitmapFactory.Options 設置 inSampleSize 實現(xiàn)采樣壓縮
* @param filePath 圖片文件的本地路徑
* @param reqWidth 期望寬度
* @param reqHeight 期望高度
* @return 采樣壓縮后的 Bitmap
*/
public static Bitmap compressImageBySampling(String filePath, int reqWidth, int reqHeight) {
// 第一次讀取設置 inJustDecodeBounds 為 true 僅獲取圖片尺寸
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
// 計算 inSampleSize 值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 第二次讀取設置 inJustDecodeBounds 為 false 獲取實際 Bitmap
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filePath, options);
}
/**
* 根據原始圖片尺寸和目標尺寸計算合理的 inSampleSize
*/
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// 原始圖片寬高
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 計算寬高比例
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// 保證壓縮后寬高仍大于目標寬高
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
* 利用 Bitmap.compress() 方法實現(xiàn)質量壓縮
* @param bitmap 原始 Bitmap
* @param format 壓縮格式(JPEG、PNG、WEBP)
* @param quality 壓縮質量(0-100,100為最高質量)
* @return 壓縮后的 Bitmap
*/
public static Bitmap compressImageByQuality(Bitmap bitmap, Bitmap.CompressFormat format, int quality) {
if (bitmap == null) return null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(format, quality, baos);
byte[] data = baos.toByteArray();
return BitmapFactory.decodeByteArray(data, 0, data.length);
}
/**
* 將 Bitmap 保存為文件
* @param bitmap 待保存的 Bitmap
* @param destFile 保存目標文件
* @param format 保存格式
* @param quality 保存質量
* @return true 保存成功,false 保存失敗
*/
public static boolean saveBitmapToFile(Bitmap bitmap, File destFile, Bitmap.CompressFormat format, int quality) {
if (bitmap == null || destFile == null) {
return false;
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(destFile);
bitmap.compress(format, quality, fos);
fos.flush();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
if (fos != null) {
try { fos.close(); } catch (Exception ex) { }
}
}
}
}
// ===========================================
// 文件: MainActivity.java
// 描述: 示例 Activity,展示如何選擇本地圖片、進行壓縮預覽和保存壓縮結果
// ===========================================
package com.example.uploaddemo;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.File;
import java.io.InputStream;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE_SELECT_IMAGE = 100;
private ImageView ivOriginal;
private ImageView ivCompressed;
private Button btnCompress;
private Uri selectedImageUri;
private Bitmap originalBitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 設置布局文件 activity_main.xml
setContentView(R.layout.activity_main);
ivOriginal = findViewById(R.id.iv_original);
ivCompressed = findViewById(R.id.iv_compressed);
btnCompress = findViewById(R.id.btn_compress);
// 點擊原始圖片區(qū)域選擇圖片
ivOriginal.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, REQUEST_CODE_SELECT_IMAGE);
}
});
// 點擊壓縮按鈕執(zhí)行圖片壓縮,采用質量壓縮方式示例
btnCompress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(originalBitmap != null) {
// 異步任務執(zhí)行壓縮操作,防止阻塞主線程
new CompressImageTask().execute();
} else {
Toast.makeText(MainActivity.this, "請先選擇一張圖片", Toast.LENGTH_SHORT).show();
}
}
});
// 檢查存儲讀取權限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, 1);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == REQUEST_CODE_SELECT_IMAGE && resultCode == RESULT_OK && data != null) {
selectedImageUri = data.getData();
try {
// 加載預覽 Bitmap
InputStream inputStream = getContentResolver().openInputStream(selectedImageUri);
originalBitmap = BitmapFactory.decodeStream(inputStream);
ivOriginal.setImageBitmap(originalBitmap);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* CompressImageTask 異步任務,后臺執(zhí)行圖片壓縮操作
*/
private class CompressImageTask extends AsyncTask<Void, Void, Bitmap> {
@Override
protected void onPreExecute() {
Toast.makeText(MainActivity.this, "開始壓縮圖片", Toast.LENGTH_SHORT).show();
}
@Override
protected Bitmap doInBackground(Void... voids) {
// 示例:先采樣壓縮,再進行質量壓縮
String imagePath = PathUtil.getRealPathFromURI(MainActivity.this, selectedImageUri);
// 通過采樣降低分辨率(例如目標尺寸 800x800)
Bitmap sampledBitmap = ImageCompressionUtils.compressImageBySampling(imagePath, 800, 800);
// 然后進行質量壓縮,設定 JPEG 格式和 70 的壓縮質量
return ImageCompressionUtils.compressImageByQuality(sampledBitmap, Bitmap.CompressFormat.JPEG, 70);
}
@Override
protected void onPostExecute(Bitmap compressedBitmap) {
if(compressedBitmap != null) {
ivCompressed.setImageBitmap(compressedBitmap);
// 保存壓縮后的圖片到本地(保存目錄與文件名自定義)
File dir = getExternalFilesDir("CompressedImages");
if(dir != null && !dir.exists()) {
dir.mkdirs();
}
File outFile = new File(dir, System.currentTimeMillis() + ".jpg");
boolean success = ImageCompressionUtils.saveBitmapToFile(compressedBitmap, outFile, Bitmap.CompressFormat.JPEG, 70);
if(success) {
Toast.makeText(MainActivity.this, "壓縮圖片保存成功:" + outFile.getAbsolutePath(), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(MainActivity.this, "壓縮圖片保存失敗", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(MainActivity.this, "圖片壓縮失敗", Toast.LENGTH_SHORT).show();
}
}
}
}
// ===========================================
// 文件: PathUtil.java
// 描述: 工具類,從圖片 URI 中獲取實際文件路徑,適配不同 Android 版本
// ===========================================
package com.example.uploaddemo;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
public class PathUtil {
public static String getRealPathFromURI(Context context, Uri contentUri) {
String[] proj = { MediaStore.Images.Media.DATA };
Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
if(cursor != null){
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
String path = cursor.getString(column_index);
cursor.close();
return path;
}
return null;
}
}5.2 XML 資源文件實現(xiàn)
<!-- ===========================================
文件: activity_main.xml
描述: MainActivity 的布局文件,包含原始圖片預覽、壓縮后圖片預覽和上傳按鈕
=========================================== -->
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:id="@+id/tv_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="點擊下方圖片選擇原始圖片"
android:textSize="18sp"
android:layout_marginBottom="8dp"/>
<ImageView
android:id="@+id/iv_original"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#EEEEEE"
android:scaleType="centerCrop"
android:layout_marginBottom="16dp"
android:contentDescription="原始圖片預覽"/>
<Button
android:id="@+id/btn_compress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="壓縮圖片"
android:layout_marginBottom="16dp"/>
<TextView
android:id="@+id/tv_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="壓縮后圖片預覽"
android:textSize="18sp"
android:layout_marginBottom="8dp"/>
<ImageView
android:id="@+id/iv_compressed"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#EEEEEE"
android:scaleType="centerCrop"
android:contentDescription="壓縮后圖片預覽"/>
</LinearLayout>
</ScrollView>
<!-- ===========================================
文件: colors.xml
描述: 定義項目中使用的顏色資源
=========================================== -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="white">#FFFFFF</color>
<color name="primary">#3F51B5</color>
</resources>
<!-- ===========================================
文件: styles.xml
描述: 定義應用主題與樣式資源,采用 AppCompat 主題
=========================================== -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@color/white</item>
<item name="android:textColorPrimary">@color/primary</item>
</style>
</resources>6. 代碼解讀與詳細講解
6.1 圖片采樣與內存控制
BitmapFactory.Options 的使用
在 ImageCompressionUtils.compressImageBySampling() 中,先通過設置 inJustDecodeBounds 讀取圖片的原始尺寸,再計算合適的 inSampleSize 值,實現(xiàn)按比例采樣加載圖片,降低內存使用。采樣與質量壓縮的結合
通過先采樣壓縮降低分辨率,再調用 Bitmap.compress() 進行質量壓縮,可大幅減少圖片體積,同時保持較好視覺效果。
6.2 Bitmap.compress() 方法及參數說明
壓縮格式與質量
使用 Bitmap.compress() 方法,可以選擇 JPEG、PNG 或 WEBP 格式,并通過 quality 參數設定壓縮質量(0~100);內存與性能考量
結合 inSampleSize 和 compress() 方法,既能減少圖片內存占用,又能加快文件上傳及顯示速度,平衡品質與性能。
6.3 異步任務與 UI 更新
AsyncTask 應用
在 MainActivity 中,通過 UploadTask 異步任務在子線程中進行圖片壓縮操作,并在 onPostExecute() 中更新壓縮結果展示于 ImageView,同時可保存文件或分享。路徑解析與文件保存
使用 PathUtil 工具類從圖片 URI 中獲取實際文件路徑,利用 ImageCompressionUtils.saveBitmapToFile() 方法將壓縮后的 Bitmap 保存到指定目錄。
7. 性能優(yōu)化與調試技巧
7.1 內存管理與 Bitmap 重用
BitmapFactory.Options 采樣
加載大圖時使用采樣技術降低圖片分辨率,減少內存占用,避免 OOM;對象復用
在圖片壓縮過程中盡量重用 Paint、ByteArrayOutputStream 等對象,減少頻繁創(chuàng)建和垃圾回收。
7.2 異步任務與線程優(yōu)化
后臺執(zhí)行
圖片壓縮等耗時操作在 AsyncTask 或其他異步方式中執(zhí)行,確保 UI 主線程流暢;網絡與 IO 性能
若后續(xù)涉及上傳,建議結合 OkHttp、Retrofit 實現(xiàn)高效網絡通信。
7.3 日志調試與異常捕獲
日志輸出
在關鍵步驟(如圖片加載、壓縮、文件保存)加入日志調試信息,方便使用 Logcat 分析問題;異常處理
為 Bitmap 操作、文件 IO 加入 try-catch 機制,確保因內存、權限等問題引起異常時能及時反饋。
8. 項目總結與未來展望
8.1 項目總結
本項目詳細介紹了如何在 Android 應用中實現(xiàn)對本地圖片的壓縮功能,包括采樣壓縮和質量壓縮兩種方式。主要成果包括:
圖片加載與采樣技術
通過 BitmapFactory.Options 計算 inSampleSize,實現(xiàn)圖片采樣加載,降低內存占用;
質量壓縮實現(xiàn)
利用 Bitmap.compress() 方法,將 Bitmap 壓縮為 JPEG(或其它格式),自定義壓縮質量參數達到文件體積和畫質間的平衡;
異步任務與結果展示
采用 AsyncTask 處理圖片壓縮,避免在 UI 線程中執(zhí)行耗時操作,并將結果通過 ImageView 實時展示;
模塊化代碼結構
將圖片選擇、壓縮處理、文件保存等功能模塊化封裝在獨立的工具類中,所有代碼均整合在一起,結構清晰便于后續(xù)擴展。
8.2 未來擴展與優(yōu)化方向
未來可以進一步在以下方面擴展與優(yōu)化本項目:
批量圖片壓縮
擴展為支持多圖片批量處理,并實現(xiàn)壓縮進度顯示和隊列管理;
更高效的異步處理
利用 RxJava、Kotlin Coroutine 或 WorkManager 進行后臺任務調度,提高代碼響應性;
動態(tài)調整壓縮參數
提供界面允許用戶自定義采樣率、壓縮格式和質量參數,并實時預覽壓縮效果;
與圖片上傳結合
整合圖片壓縮與上傳功能,縮小上傳文件體積,提高網絡傳輸效率;
質量檢測與優(yōu)化
結合圖像處理算法,自動檢測壓縮后圖片質量,調整壓縮策略,兼顧視覺效果與文件大??;
異常和錯誤處理
構建完善的錯誤捕獲和重試機制,在圖片處理或文件保存失敗時給予詳細反饋和自動重試。
以上就是Android實現(xiàn)壓縮本地圖片功能的詳細內容,更多關于Android壓縮本地圖片的資料請關注腳本之家其它相關文章!
相關文章
Android Flutter利用CustomPaint繪制基本圖形詳解
CustomPaint其實和前端的Canvas基本上是一樣的,前端Canvas支持的繪制方法CustomPaint都支持,畢竟CustomPaint其實也是基于Canvas實現(xiàn)的。本篇我們來介紹 CustomPaint 基本圖形的繪制,感興趣的可以了解一下2022-07-07
Android編程實現(xiàn)控件不同狀態(tài)文字顯示不同顏色的方法
這篇文章主要介紹了Android編程實現(xiàn)控件不同狀態(tài)文字顯示不同顏色的方法,涉及Android針對控件布局文件屬性設置及狀態(tài)判定等相關技巧,需要的朋友可以參考下2016-02-02
Android自定義Chronometer實現(xiàn)短信驗證碼秒表倒計時功能
這篇文章主要介紹了Android自定義ChronometerView實現(xiàn)類似秒表倒計時,短信驗證碼倒計時功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11
Android貝塞爾曲線實現(xiàn)填充不規(guī)則圖形并隨手指運動
這篇文章主要為大家詳細介紹了Android貝塞爾曲線實現(xiàn)填充不規(guī)則圖形,并隨手指運動,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-09-09

