Android實(shí)現(xiàn)本地圖片選擇及預(yù)覽縮放效果
在做項(xiàng)目時(shí)經(jīng)常會(huì)遇到選擇本地圖片的需求,以前都是懶得寫直接調(diào)用系統(tǒng)方法來選擇圖片,但是這樣并不能實(shí)現(xiàn)多選效果,最近又遇到了,所以還是寫一個(gè)demo好了,以后也方便使用。還是首先來看看效果:
顯示的圖片使用RecyclerView實(shí)現(xiàn)的,利用Glide來加載;下面彈出的圖片文件夾效果是采用PopupWindow實(shí)現(xiàn),這里比采用PopupWindow更方便,彈出顯示的左邊圖片是這個(gè)文件夾里的第一張圖片;選中的圖片可以進(jìn)行預(yù)覽,使用網(wǎng)上一個(gè)大神寫的來實(shí)現(xiàn)的;至于圖片的獲取是用ContentProvider。
看看主界面的布局文件,上面一欄是一個(gè)返回按鈕和一個(gè)跳轉(zhuǎn)預(yù)覽界面的按鈕,根據(jù)是否有選中的圖片來設(shè)置它的點(diǎn)擊和顯示狀態(tài);中間就是一個(gè)用于顯示圖片的RecyclerView,左下角是顯示文件夾的名字可點(diǎn)擊切換,右下角就是確定按鈕。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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:orientation="vertical" tools:context="com.cdxsc.imageselect_y.ImageSelecteActivity"> <RelativeLayout android:layout_width="match_parent" android:layout_height="45dp" android:background="@android:color/white"> <ImageButton android:id="@+id/ib_back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:background="@mipmap/action_bar_back_normal" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:layout_toRightOf="@id/ib_back" android:text="選擇圖片" android:textColor="#000" android:textSize="16sp" /> <TextView android:id="@+id/tv_preview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="10dp" android:enabled="false" android:text="預(yù)覽" android:textColor="#BEBFBF" android:textSize="16sp" /> </RelativeLayout> <View android:layout_width="match_parent" android:layout_height="0.5dp" android:background="#eeeeee" /> <android.support.v7.widget.RecyclerView android:id="@+id/rv" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"></android.support.v7.widget.RecyclerView> <RelativeLayout android:layout_width="match_parent" android:layout_height="50dp"> <TextView android:id="@+id/tv_allPic" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:clickable="true" android:gravity="center_vertical" android:text="所有圖片" android:textColor="@android:color/black" android:textSize="16sp" /> <Button android:id="@+id/bt_confirm" android:layout_width="wrap_content" android:layout_height="35dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="10dp" android:background="@drawable/shape_disable" android:enabled="false" android:text="確定" android:textColor="#676767" android:textSize="16sp" /> </RelativeLayout> </LinearLayout>
好了,現(xiàn)在看主界面的代碼
public class ImageSelecteActivity extends AppCompatActivity { private static final String TAG = "lzy"; @BindView(R.id.ib_back) ImageButton mButtonBack; @BindView(R.id.tv_preview) TextView mTextViewPreview; @BindView(R.id.rv) RecyclerView mRecyclerView; @BindView(R.id.tv_allPic) TextView mTextViewAllPic; @BindView(R.id.bt_confirm) Button mButtonConfirm; private GalleryPopupWindow mPopupWindow; //存儲(chǔ)每個(gè)目錄下的圖片路徑,key是文件名 private Map<String, List<String>> mGroupMap = new HashMap<>(); private List<ImageBean> list = new ArrayList<>(); //當(dāng)前文件夾顯示的圖片路徑 private List<String> listPath = new ArrayList<>(); //所選擇的圖片路徑集合 private ArrayList<String> listSelectedPath = new ArrayList<>(); private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { //掃描完成后 getGalleryList(); listPath.clear(); listPath.addAll(mGroupMap.get("所有圖片")); adapter.update(listPath); if (mPopupWindow != null) mPopupWindow.notifyDataChanged(); } }; private ImageSelectAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_image_selecte); ButterKnife.bind(this); init(); } private void init() { getImages(); mRecyclerView.setLayoutManager(new GridLayoutManager(ImageSelecteActivity.this, 3)); adapter = new ImageSelectAdapter(this, listPath); mRecyclerView.setAdapter(adapter); adapter.setOnCheckedChangedListener(onCheckedChangedListener); } @OnClick({R.id.ib_back, R.id.tv_preview, R.id.tv_allPic, R.id.bt_confirm}) public void onClick(View view) { switch (view.getId()) { case R.id.ib_back: finish(); break; case R.id.tv_preview://跳轉(zhuǎn)預(yù)覽界面 Intent intent = new Intent(ImageSelecteActivity.this, ImagePreviewActivity.class); //把選中的圖片集合傳入預(yù)覽界面 intent.putStringArrayListExtra("pic", listSelectedPath); startActivity(intent); break; case R.id.tv_allPic://選擇圖片文件夾 if (mPopupWindow == null) { //把文件夾列表的集合傳入顯示 mPopupWindow = new GalleryPopupWindow(this, list); mPopupWindow.setOnItemClickListener(new GalleryPopupWindow.OnItemClickListener() { @Override public void onItemClick(String fileName) { //切換了文件夾,清除之前的選擇的信息 setButtonDisable(); listPath.clear(); listSelectedPath.clear(); //把當(dāng)前選擇的文件夾內(nèi)圖片的路徑放入listPath,更新界面 listPath.addAll(mGroupMap.get(fileName)); adapter.update(listPath); mTextViewAllPic.setText(fileName); } }); } mPopupWindow.showAtLocation(mRecyclerView, Gravity.BOTTOM, 0, dp2px(50, ImageSelecteActivity.this)); break; case R.id.bt_confirm://確定 for (int i = 0; i < listSelectedPath.size(); i++) { //這里可通過Glide把它轉(zhuǎn)為Bitmap Glide.with(this).load("file://" + listSelectedPath.get(i)).asBitmap().into(new SimpleTarget<Bitmap>() { @Override public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) { Log.i(TAG, "onResourceReady: " + resource); } }); } break; } } /** * dp轉(zhuǎn)px */ public static int dp2px(int dp, Context context) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()); } //選擇圖片變化的監(jiān)聽 private ImageSelectAdapter.OnCheckedChangedListener onCheckedChangedListener = new ImageSelectAdapter.OnCheckedChangedListener() { @Override public void onChanged(boolean isChecked, String path, CheckBox cb, int position) { if (isChecked) {//選中 if (listSelectedPath.size() == 9) { Toast.makeText(ImageSelecteActivity.this, "最多選擇9張圖片", Toast.LENGTH_SHORT).show(); //把點(diǎn)擊變?yōu)閏hecked的圖片變?yōu)闆]有checked cb.setChecked(false); adapter.setCheckedBoxFalse(position); return; } //選中的圖片路徑加入集合 listSelectedPath.add(path); } else {//取消選中 //從集合中移除 if (listSelectedPath.contains(path)) listSelectedPath.remove(path); } //如果沒有選中的按鈕不可點(diǎn)擊 if (listSelectedPath.size() == 0) { setButtonDisable(); } else { setButtonEnable(); } } }; //選中圖片時(shí)的按鈕狀態(tài) private void setButtonEnable() { mButtonConfirm.setBackgroundResource(R.drawable.selector_bt); mButtonConfirm.setTextColor(Color.parseColor("#ffffff")); mButtonConfirm.setEnabled(true); mTextViewPreview.setEnabled(true); mTextViewPreview.setTextColor(getResources().getColor(R.color.colorAccent)); mButtonConfirm.setText("確定" + listSelectedPath.size() + "/9"); } //沒有選擇時(shí)按鈕狀態(tài) private void setButtonDisable() { mButtonConfirm.setBackgroundResource(R.drawable.shape_disable); mButtonConfirm.setTextColor(Color.parseColor("#676767")); mButtonConfirm.setEnabled(false); mTextViewPreview.setEnabled(false); mTextViewPreview.setTextColor(Color.parseColor("#BEBFBF")); mButtonConfirm.setText("確定"); } /** * 利用ContentProvider掃描手機(jī)中的圖片,此方法在運(yùn)行在子線程中 */ private void getImages() { new Thread(new Runnable() { @Override public void run() { Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; ContentResolver mContentResolver = ImageSelecteActivity.this.getContentResolver(); //只查詢jpeg和png的圖片 // Cursor mCursor = mContentResolver.query(mImageUri, null, // MediaStore.Images.Media.MIME_TYPE + "=? or " // + MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?", // new String[]{"image/jpeg", "image/png", "image/jpg"}, MediaStore.Images.Media.DATE_MODIFIED); Cursor mCursor = mContentResolver.query(mImageUri, null, null, null, MediaStore.Images.Media.DATE_MODIFIED); if (mCursor == null) { return; } //存放所有圖片的路徑 List<String> listAllPic = new ArrayList<String>(); while (mCursor.moveToNext()) { //獲取圖片的路徑 String path = mCursor.getString(mCursor .getColumnIndex(MediaStore.Images.Media.DATA)); //獲取該圖片的父路徑名 String parentName = new File(path).getParentFile().getName(); listAllPic.add(path); //根據(jù)父路徑名將圖片放入到mGruopMap中 if (!mGroupMap.containsKey(parentName)) { List<String> chileList = new ArrayList<String>(); chileList.add(path); mGroupMap.put(parentName, chileList); } else { mGroupMap.get(parentName).add(path); } } //添加所有圖片 mGroupMap.put("所有圖片", listAllPic); //通知Handler掃描圖片完成 mHandler.sendEmptyMessage(0); mCursor.close(); } }).start(); } //獲取相冊(cè)文件夾列表 private void getGalleryList() { Iterator<Map.Entry<String, List<String>>> iterator = mGroupMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, List<String>> next = iterator.next(); ImageBean imageBean = new ImageBean(); imageBean.setFileName(next.getKey()); imageBean.setFirstPicPath(next.getValue().get(0)); imageBean.setCount(next.getValue().size()); if (next.getKey().equals("所有圖片")) list.add(0, imageBean); else list.add(imageBean); } } }
·mGroupMap:這個(gè)是以文件夾名為key,文件夾內(nèi)的圖片路徑集合為value,也就是按照文件夾來分別存儲(chǔ)了所有圖片的路徑。
·listPath:保存的是當(dāng)前顯示在界面上的文件夾內(nèi)的圖片路徑集合
·listSelectedPath:保存用戶選中的圖片路徑
·list:保存的是ImageBean的集合,ImageBean保存了文件夾名、里面首張圖片的路徑以及里面所包含圖片的數(shù)量,當(dāng)切換文件夾時(shí)用于顯示
·getImages():這個(gè)方法就是用來掃描手機(jī)里圖片并保存的,這是在子線程中執(zhí)行的,顯示這可能是一個(gè)耗時(shí)的任務(wù)。通過ContentProvider獲取到一個(gè)包含所有圖片的Cursor,然后遍歷這個(gè)Cursor把所需的數(shù)據(jù)就保存在mGroupMap里面,最后利用Handler通知界面更新。
·getGalleryList():這個(gè)方法就是mGroupMap里面的數(shù)據(jù)來給list賦值,也就是產(chǎn)生一個(gè)現(xiàn)實(shí)文件夾列表所需的數(shù)據(jù)集合。
·GalleryPopupWindow也就是我們用于顯示文件列表的,在67--84行就是一些GalleryPopupWindow的設(shè)置,調(diào)用showAtLocation方法把PopupWindow顯示在距離底部50dp的位置,并設(shè)置了點(diǎn)擊的回調(diào),當(dāng)切換了一個(gè)文件夾后要做的相關(guān)操作就在這里進(jìn)行。GalleryPopupWindow再待會(huì)再具體看看
接下來再看看中間RecyclerView的Adapter
public class ImageSelectAdapter extends RecyclerView.Adapter<ImageSelectAdapter.NViewHolder> { private Context context; private List<String> list = new ArrayList<>(); private OnCheckedChangedListener onCheckedChangedListener; private List<Boolean> listChecked = new ArrayList<>(); public ImageSelectAdapter(Context context, List<String> list) { this.context = context; this.list.addAll(list); setListCheched(list); } public void update(List<String> list) { this.list.clear(); this.list.addAll(list); setListCheched(list); notifyDataSetChanged(); } /** * 設(shè)置listChecked的初始值 * * @param list */ private void setListCheched(List<String> list) { listChecked.clear(); for (int i = 0; i < list.size(); i++) { listChecked.add(false); } } //當(dāng)點(diǎn)擊超過了九張圖片,再點(diǎn)擊的設(shè)置為false public void setCheckedBoxFalse(int pos) { listChecked.set(pos, false); } public interface OnCheckedChangedListener { /** * @param isChecked 是否選中 * @param path 點(diǎn)擊的圖片路徑 * @param cb 點(diǎn)擊的CheckBox * @param pos 點(diǎn)擊的位置 */ void onChanged(boolean isChecked, String path, CheckBox cb, int pos); } public void setOnCheckedChangedListener(OnCheckedChangedListener onCheckedChangedListener) { this.onCheckedChangedListener = onCheckedChangedListener; } @Override public NViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new NViewHolder(LayoutInflater.from(context).inflate(R.layout.item_image_select, parent, false)); } @Override public void onBindViewHolder(final NViewHolder holder, final int position) { Glide.with(context).load("file://" + list.get(position)).into(holder.iv); holder.cb.setChecked(listChecked.get(position)); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { holder.cb.setChecked(!holder.cb.isChecked()); if (holder.cb.isChecked()) { listChecked.set(position, true); } else { listChecked.set(position, false); } if (onCheckedChangedListener != null) { onCheckedChangedListener.onChanged(holder.cb.isChecked(), list.get(position), holder.cb, position); } } }); } @Override public int getItemCount() { return list.size(); } public class NViewHolder extends RecyclerView.ViewHolder { @BindView(R.id.iv_itemImageSelect) ImageView iv; @BindView(R.id.cb_itemImageSelect) CheckBox cb; public NViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } } }
這里Item的布局文件就是一個(gè)ImageView加一個(gè)CheckBox,根據(jù)選中狀態(tài)改變CheckBox的狀態(tài),這里就不貼出來了。
·listChecked:這個(gè)集合是用來存儲(chǔ)每個(gè)位置是否Check的,如果在onBindViewHolder里面不設(shè)置CheckBox的狀態(tài)的話,由于復(fù)用問題會(huì)出問題,所以想出了用一個(gè)集合來保存它們狀態(tài)的方法,不知道大家有沒有其他更好的方法。
·OnCheckedChangedListener:向外暴露的接口,把點(diǎn)擊的位置等參數(shù)都傳到Activity中去。
·update():這個(gè)方法用來更新界面的,沒有采用直接調(diào)notifyDataSetChanged方法是因?yàn)?,如果?shù)據(jù)的數(shù)量變化了那么listChecked的數(shù)量也要發(fā)生變化才行這樣才能對(duì)應(yīng),所以寫了這個(gè)方法。
再接著看看GalleryPopupWindow
/** * Created by lzy on 2017/2/8. */ public class GalleryPopupWindow extends PopupWindow { private static final String TAG = "lzy"; RecyclerView mRecyclerView; private Activity activity; private GalleryPopupWindow.OnItemClickListener onItemClickListener; private List<ImageBean> list; private GalleryAdapter adapter; public GalleryPopupWindow(Activity context, List<ImageBean> list) { super(context); this.activity = context; this.list = list; LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View contentView = inflater.inflate(R.layout.popu_gallery, null); initView(contentView); int h = context.getWindowManager().getDefaultDisplay().getHeight(); int w = context.getWindowManager().getDefaultDisplay().getWidth(); this.setContentView(contentView); this.setWidth(w); this.setHeight(ImageSelecteActivity.dp2px(350, context)); this.setFocusable(false); this.setOutsideTouchable(true); this.update(); setBackgroundDrawable(new ColorDrawable(000000000)); } public void notifyDataChanged() { adapter.notifyDataSetChanged(); } private void initView(View contentView) { mRecyclerView = (RecyclerView) contentView.findViewById(R.id.rv_gallery); mRecyclerView.setLayoutManager(new LinearLayoutManager(activity)); adapter = new GalleryAdapter(list, activity); adapter.setOnItemClickListener(new GalleryAdapter.OnItemClickListener() { @Override public void onItemClick(String fileName) { if (onItemClickListener != null) { onItemClickListener.onItemClick(fileName); dismiss(); } } }); mRecyclerView.setAdapter(adapter); } //暴露點(diǎn)擊的接口 public interface OnItemClickListener { /** * @param keyValue */ void onItemClick(String keyValue); } public void setOnItemClickListener(OnItemClickListener onItemClickListener) { this.onItemClickListener = onItemClickListener; } }
這個(gè)PopupWindow的布局文件就是一個(gè)RecyclerView,所以這里面也沒什么,也就是設(shè)置RecyclerView,然后向外暴露一個(gè)點(diǎn)擊的接口,用于Activity接收是點(diǎn)擊了哪個(gè)文件夾,所以接口參數(shù)也就是文件夾名,再看看這個(gè)PopupWindow的Adapter
/** * Created by lzy on 2017/2/8. */ public class GalleryAdapter extends RecyclerView.Adapter<GalleryAdapter.NViewHolder> { private Context context; private List<ImageBean> list; private OnItemClickListener onItemClickListener; //用于記錄是選中的哪一個(gè)文件夾 private int selectedPos; public GalleryAdapter(List<ImageBean> list, Context context) { this.list = list; this.context = context; } public interface OnItemClickListener { void onItemClick(String fileName); } public void setOnItemClickListener(OnItemClickListener onItemClickListener) { this.onItemClickListener = onItemClickListener; } @Override public NViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new NViewHolder(LayoutInflater.from(context).inflate(R.layout.item_gallery, parent, false)); } @Override public void onBindViewHolder(NViewHolder holder, final int position) { holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { selectedPos = position; notifyDataSetChanged(); if (onItemClickListener != null) { onItemClickListener.onItemClick(list.get(position).getFileName()); } } }); if (position == selectedPos) { holder.ivCheck.setVisibility(View.VISIBLE); } else { holder.ivCheck.setVisibility(View.GONE); } holder.tvCount.setText(list.get(position).getCount() + "張"); holder.tvName.setText(list.get(position).getFileName()); Glide.with(context).load("file://" + list.get(position).getFirstPicPath()).into(holder.iv); } @Override public int getItemCount() { return list.size(); } public class NViewHolder extends RecyclerView.ViewHolder { @BindView(R.id.iv_itemGallery) ImageView iv; @BindView(R.id.tv_itemGallery_name) TextView tvName; @BindView(R.id.tv_itemGallery_count) TextView tvCount; @BindView(R.id.iv_itemGallery_check) ImageView ivCheck; public NViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } } }
這里有個(gè)接口是把點(diǎn)擊的文件名傳遞給PopupWindow,然后再給Activity,selectedPos是用來記錄選擇的是哪一個(gè)文件夾,顯示對(duì)應(yīng)的CheckBox。
這里就差不多完成了,感興趣的可以下載Demo來看看。再說一下,這里顯示圖片都是采用的Glide,使用也很方便,我們獲取的圖片路徑都是文件路徑,如果要轉(zhuǎn)化為Bitmap也可以直接調(diào)用Glide的方法就可以輕松實(shí)現(xiàn),如下所示:
Glide.with(this).load("file://" + listSelectedPath.get(i)).asBitmap().into(new SimpleTarget<Bitmap>() { @Override public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) { Log.i(TAG, "onResourceReady: " + resource); } });
其中找尋控件都沒有使用findViewById,而是采用的ButterKnife,節(jié)約了大量的時(shí)間,順便說說導(dǎo)入的方法
在app下面的build.gradle中加入以下:
apply plugin: 'com.neenbedankt.android-apt'
apt 'com.jakewharton:butterknife-compiler:8.1.0'
compile 'com.github.bumptech.glide:glide:3.5.2'
項(xiàng)目下面的build.gradle
//添加apt插件 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
添加插件
File->Setting->Plugins 搜索zelezny,如下所示
當(dāng)需要使用的時(shí)候,直接在光標(biāo)移動(dòng)到布局文件,點(diǎn)擊Alt+Insert,選擇Generate ButterKnife Injections
就出現(xiàn)如下界面,可以自動(dòng)生成了
源碼地址:Android圖片選擇及預(yù)覽縮放
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android動(dòng)態(tài)添加view的方法示例
本篇文章主要介紹了Android動(dòng)態(tài)添加view的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10android閱讀器長(zhǎng)按選擇文字功能實(shí)現(xiàn)代碼
本篇文章主要介紹了android閱讀器長(zhǎng)按選擇文字功能實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07手把手教學(xué)Android用jsoup解析html實(shí)例
本篇文章主要介紹了手把手教學(xué)Android用jsoup解析html實(shí)例,jsoup 是一款Java 的HTML解析器。具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Android自定義view系列之99.99%實(shí)現(xiàn)QQ側(cè)滑刪除效果實(shí)例代碼詳解
這篇文章給大家介紹android自定義view系列之99.99%實(shí)現(xiàn)QQ側(cè)滑刪除效果,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友參考下吧2016-09-09Android實(shí)現(xiàn)水波紋擴(kuò)散效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)水波紋擴(kuò)散效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01Android開發(fā)中一個(gè)簡(jiǎn)單實(shí)用的調(diào)試應(yīng)用技巧分享
這篇文章主要跟大家分享了一個(gè)簡(jiǎn)單實(shí)用的Android調(diào)試應(yīng)用技巧,文中介紹的非常詳細(xì),相信對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友下面來一起看看吧。2017-05-05Android實(shí)現(xiàn)中軸旋轉(zhuǎn)特效 Android制作別樣的圖片瀏覽器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)中軸旋轉(zhuǎn)特效,制作別樣的圖片瀏覽器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11Android實(shí)現(xiàn)類似iOS分欄控制器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)類似iOS分欄控制器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03