圖片選擇器

一 翔曲、設(shè)計目標

在進行開發(fā)的時候有現(xiàn)成的圖片選擇器,通過內(nèi)容提供器可以直接打開系統(tǒng)相冊進行選擇,得到要選擇的圖片,界面比較簡陋,但可以實現(xiàn)簡單的圖片選擇功能惧财。要設(shè)計一個自己的圖片選擇器偎漫,要明確設(shè)計的目標:

  1. 訪問手機的媒體庫压怠,獲得有圖片的文件夾
  2. 為文件夾列表設(shè)置一個下拉選擇框杯巨,下拉選擇要訪問的文件夾
  3. 選擇目標文件夾剖张,遍歷文件夾所有圖片畅形,利用Glide進行圖片加載最后在RecyclerView中進行顯示
  4. 設(shè)置一個ImageView對要選擇的圖片進行預(yù)覽蛉顽,點擊預(yù)覽圖進入圖片編輯頁面

以上大概就是我要實現(xiàn)的圖片選擇器的設(shè)計目標护姆,對設(shè)計目標進行分析后確定使用的工具有

  1. MediaStore媒體庫
  2. Handler異步加載
  3. Glide圖片加載
  4. RecyclerView列表顯示
  5. Spinner下拉選擇框

二矾端、 相關(guān)內(nèi)容學(xué)習(xí)

1. MediaStore

MediaStore這個類是android系統(tǒng)提供的一個多媒體數(shù)據(jù)庫,android 中多媒體信息都可以從這里提取卵皂。這個MediaStore包括了多媒體數(shù)據(jù)庫的所有信息秩铆,包括音頻,視頻和圖像,android把所有的多媒體數(shù)據(jù)庫接口 進行了封裝,所有的數(shù)據(jù)庫不用自己進行創(chuàng)建殴玛,直接調(diào)用利用ContentResolver去掉用那些封裝好的接口就可以進行數(shù)據(jù)庫的操作了捅膘。今天我就介紹 一些這些接口的用法。在進行圖片選擇的時候滚粟,就是通過MediaStore進行查找圖片路徑等操作寻仗。

類結(jié)構(gòu)圖

MediaStore詳細字段參考鏈接:https://blog.csdn.net/lemon_blue/article/details/52353851

2. Handler異步加載

Handler主要用于異步消息的處理: 有點類似輔助類,封裝了消息投遞凡壤、消息處理等接口署尤。當發(fā)出一個消息之后,首先進入一個消息隊列亚侠,發(fā)送消息的函數(shù)即刻返回曹体,而另外一個部分在消息隊列中逐一將消息取出,然后對消息進行處理盖奈,也就是發(fā)送消息和接收消息不是同步的處理混坞。 這種機制通常用來處理相對耗時比較長的操作。

3. Glide圖片加載

一行代碼

Glide.with(context).load(url).into(imageView);

4. RecyclerView

參考之前的內(nèi)容

5. Spinner控件

參考代碼钢坦,spinner的適配器是ArrayAdapter<String>類型。

三啥酱、 代碼分析

1. 圖片工具類

定義一個工具類對媒體庫進行查詢爹凹,返回所以包含圖片的文件夾列表,注釋比較詳細镶殷。

public class ImageUtils {
    /**
     * 圖片文件夾名列表
     */
    public static List<String> folderNameList = new ArrayList<> ();

    /**
     * 圖片文件夾路徑列表
     */
    public static List<String> folderPathList = new ArrayList<> ();

    /**
     * 文件夾列表禾酱,用于判斷當前文件夾是否遍歷
     */
    public static HashSet<String> mFolderList = new HashSet<> ();

    /**
     * 得到圖片文件夾,利用ContentResolver進行遍歷
     * @param context
     * @param handler
     */
    public static void getFolder(final Context context, final Handler handler) {
        new Thread (new Runnable () {
            @Override
            public void run() {
                Uri imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                ContentResolver contentResolver = context.getContentResolver ();
                //文件夾選擇條件绘趋,MIME_TYPE媒體類型為JPEG和png的文件
                String selection = MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?";
                String[] selectionArgs = new String[]{"image/jpeg", "image/png"};
                Cursor cursor = contentResolver.query (imageUri, null, selection, selectionArgs, MediaStore.Images.Media.DATE_MODIFIED);
                while (cursor.moveToNext ()) {
                    String imagePath = cursor.getString (cursor.getColumnIndex (MediaStore.Images.Media.DATA));
                    //獲得當前文件夾路徑
                    File imageFolder = new File (imagePath).getParentFile ();
                    if(imageFolder == null) {
                        continue;
                    }
                    String folderPath = imageFolder.getAbsolutePath ();
                    //利用HashSet進行判斷當前文件夾是否被選擇
                    if(mFolderList.contains (folderPath)) {
                        continue;
                    } else {
                        mFolderList.add (folderPath);
                        folderPathList.add (folderPath);
                        //截取文件夾的最后一個"/"后面的字符串作為文件夾名稱
                        int indexOfPath = folderPath.lastIndexOf ("/");
                        String folderName = folderPath.substring (indexOfPath);
                        folderNameList.add (folderName);
                    }
                }
                cursor.close ();
                mFolderList.clear ();
                Message msg = new Message ();
                msg.what = 1;
                handler.sendMessage (msg);
            }
        }).start ();
    }
}

2. 圖片選擇器主界面

public class CreateFragment extends Fragment {

    /**
     * 圖片選擇界面的預(yù)覽圖
     */
    private ImageView iv_selectedImage;

    /**
     * 顯示當前文件夾的圖片列表
     */
    private RecyclerView recyclerView;

    /**
     * 圖片列表適配器
     */
    private CreateAdapter createAdapter;

    /**
     * 列表網(wǎng)格布局管理器
     */
    private GridLayoutManager gridLayoutManager;

    /**
     * 文件夾下拉選擇欄
     */
    private Spinner spinner;

    /**
     * 下拉選擇欄適配器
     */
    private ArrayAdapter<String> spinnerAdapter;

    /**
     * 文件夾下拉選擇欄顯示列表
     */
    private List<String> nameList = new ArrayList<> ();

    /**
     * 所有圖片文件夾路徑
     */
    private List<String> imageList = new ArrayList<> ();

    /**
     * 當前文件夾下所有圖片路徑
     */
    private static List<String> selectedImageList = new ArrayList<> ();

    /**
     * 當前預(yù)覽圖片路徑
     */
    private static String selectedImagePath;

    public CreateFragment() {
    }

    /**
     * 獲取當前fragment實例
     * @return
     */
    public static Fragment newInstance() {
        Bundle args = new Bundle ();
        Fragment fragment = new CreateFragment ();
        fragment.setArguments (args);
        return fragment;
    }

    /**
     * Handler處理圖片工具類得到的文件夾列表和更新UI
     */
    private Handler handler = new Handler () {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage (msg);
            if(msg.what == 1) {
                nameList = ImageUtils.folderNameList;
                imageList = ImageUtils.folderPathList;
                spinnerAdapter = new ArrayAdapter<String> (getActivity (), R.layout.support_simple_spinner_dropdown_item, nameList);
                spinner.setAdapter (spinnerAdapter);
            }
        }
    };

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate (R.layout.fragment_create, container, false);
        initView (view);
        ImageUtils.getFolder (getActivity (), handler);
        return view;
    }

    /**
     * 初始化界面
     * @param view
     */
    public void initView(View view) {
        nameList.clear ();
        iv_selectedImage = view.findViewById (R.id.iv_create_selected_image);
        //預(yù)覽圖點擊事件
        iv_selectedImage.setOnClickListener (new View.OnClickListener () {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent (getActivity (), SelectedImageActivity.class);
                intent.putExtra ("path", selectedImagePath);
                startActivity (intent);
            }
        });
        spinner = view.findViewById (R.id.create_spinner);
        recyclerView = view.findViewById (R.id.create_select_recycler_view);
        onSpinnerSelectedListener ();

    }

    /**
     * 設(shè)置下拉選擇欄的選擇事件
     */
    public void onSpinnerSelectedListener() {
        spinner.setOnItemSelectedListener (new AdapterView.OnItemSelectedListener () {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                selectedImageList.clear ();
                //利用排序工具類進行排序返回當前文件夾圖片路徑列表
                selectedImageList = SortUtils.sortImage (imageList, i);
                setRecyclerView ();
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
    }
    
    /**
     * 得到當前文件夾的圖片路徑颤陶,為RecyclerView設(shè)置適配器,進行顯示
     */
    private void setRecyclerView() {
        //布局管理器一定要設(shè)置
        gridLayoutManager = new GridLayoutManager (getActivity (), 4, GridLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager (gridLayoutManager);
        createAdapter = new CreateAdapter (getActivity (), selectedImageList);
        recyclerView.setAdapter (createAdapter);
        /**
         * 為RecyclerView的item設(shè)置點擊事件
         */
        createAdapter.setOnItemClickListener (new CreateAdapter.OnItemClickListener () {
            @Override
            public void onClick(int position) {
                selectedImagePath = selectedImageList.get (position);
                Glide.with (getActivity ()).load (selectedImagePath).into (iv_selectedImage);
            }
        });
        //加載每個文件夾的第一張圖片
        selectedImagePath = selectedImageList.get (0);
        Glide.with (getActivity ()).load (selectedImagePath).into (iv_selectedImage);
    }
}

3. 按圖片修改時間排序工具類

public class SortUtils {

    private static List<String> selectedImageList = new ArrayList<> ();
    public static List<String> sortImage(List<String> imageList,int position) {
        String imageFolder = imageList.get (position);
        //獲取當前文件夾
        File imageFile = new File (imageFolder).getAbsoluteFile ();
        //獲取當前文件夾下的文件
        File[] files = imageFile.listFiles (new FileFilter () {
            @Override
            public boolean accept(File file) {
                String imageName = file.getName ().toString ();
                if(imageName.endsWith (".jpeg") || imageName.endsWith (".jpg") || imageName.endsWith (".png")) {
                    return true;
                }
                return false;
            }
        });
        //
        List<File> fileList = new ArrayList<> ();
        for (int j = 0; j < files.length; j++) {
            fileList.add (files[j]);
        }
        Collections.sort (fileList, new FileComparator ());
        for (int j = 0; j < fileList.size (); j++) {
            selectedImageList.add (fileList.get (j).getAbsolutePath ());
        }

        return selectedImageList;
    }

    /**
     * 文件比較類陷遮,實現(xiàn)Comparator接口滓走,重寫compare方法
     * 傳入File泛型
     * public int compare(Object o1, Object o2) 返回一個基本類型的整型
     * 如果要按照升序排序,則o1< o2,返回-1(負數(shù))帽馋,相等返回0搅方,01大于02返回1(正數(shù))
     * 如果要按照降序排序,則o1< o2,返回1(正數(shù))绽族,相等返回0姨涡,01大于02返回-1(負數(shù))
     */
    public static class FileComparator implements Comparator<File> {

        /**
         * 按修改時間進行排序,lastModified()返回此抽象路徑名表示的文件最后一次被修改的時間吧慢。
         * @param file1
         * @param file2
         * @return
         */
        @Override
        public int compare(File file1, File file2) {
            if(file1.lastModified () < file2.lastModified ()) {
                return 1;
            } else {
                return -1;
            }
        }
    }
}

4. RecyclerView適配器

public class CreateAdapter extends RecyclerView.Adapter<CreateAdapter.CreateHolder> {

    /**
     * 使用適配器的上下文
     */
    private Context context;

    /**
     * 傳入適配器的顯示列表
     */
    private List<String> list;

    /**
     * 實例化一個接口
     */
    private OnItemClickListener onItemClickListener;

    public CreateAdapter(Context context, List<String> list) {
        this.context = context;
        this.list = list;

    }

    class CreateHolder extends RecyclerView.ViewHolder {
        RelativeLayout rlItemLayout;
        ImageView ivItemImage;

        public CreateHolder(View itemView) {
            super (itemView);
            rlItemLayout = itemView.findViewById (R.id.create_list_item_layout_rl);
            ivItemImage = itemView.findViewById (R.id.create_list_item_iv);
        }
    }

    @NonNull
    @Override
    public CreateHolder onCreateViewHolder(@NonNull ViewGroup parent, final int viewType) {
        View view = LayoutInflater.from (parent.getContext ()).inflate (R.layout.create_list_item_layout, parent, false);
        final CreateHolder holder = new CreateHolder (view);
        view.setOnClickListener (new View.OnClickListener () {
            @Override
            public void onClick(View view) {
                onItemClickListener.onClick ((int) view.getTag ());
            }
        });
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull CreateHolder holder, int position) {
        Glide.with (context).load (list.get (position)).into (holder.ivItemImage);
        holder.itemView.setTag (position);
    }

    @Override
    public int getItemCount() {
        return list.size ();
    }

    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onAttachedToRecyclerView (recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager ();
        if(manager instanceof GridLayoutManager) {
            GridLayoutManager gridLayoutManager = (GridLayoutManager) manager;
            gridLayoutManager.setSpanSizeLookup (new GridLayoutManager.SpanSizeLookup () {
                @Override
                public int getSpanSize(int position) {
                    return 1;
                }
            });
        }
    }

    /**
     * 定義一個item的點擊事件接口
     */
    public interface OnItemClickListener {
        /**
         * 為item添加點擊事件涛漂,傳入一個點擊的item位置
         *
         * @param position
         */
        void onClick(int position);
    }

    /**
     * 為Activity提供一個監(jiān)聽點擊事件的方法,實現(xiàn)自定義的接口
     *
     * @param onItemClickListener
     */
    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }
}

四检诗、 成果展示

image.png

image.png

image.png

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匈仗,一起剝皮案震驚了整個濱河市底哗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锚沸,老刑警劉巖跋选,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異哗蜈,居然都是意外死亡前标,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門距潘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炼列,“玉大人,你說我怎么就攤上這事音比〖蠹猓” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵洞翩,是天一觀的道長稽犁。 經(jīng)常有香客問我,道長骚亿,這世上最難降的妖魔是什么已亥? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮来屠,結(jié)果婚禮上虑椎,老公的妹妹穿的比我還像新娘。我一直安慰自己俱笛,他們只是感情好捆姜,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著迎膜,像睡著了一般泥技。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上星虹,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天零抬,我揣著相機與錄音,去河邊找鬼宽涌。 笑死平夜,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的卸亮。 我是一名探鬼主播忽妒,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了段直?” 一聲冷哼從身側(cè)響起吃溅,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鸯檬,沒想到半個月后决侈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡喧务,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年赖歌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片功茴。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡庐冯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出坎穿,到底是詐尸還是另有隱情展父,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布玲昧,位于F島的核電站栖茉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏酌呆。R本人自食惡果不足惜衡载,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望隙袁。 院中可真熱鬧,春花似錦弃榨、人聲如沸菩收。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娜饵。三九已至,卻和暖如春官辈,著一層夾襖步出監(jiān)牢的瞬間箱舞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工拳亿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留晴股,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓肺魁,卻偏偏與公主長得像电湘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 相信很多朋友在開發(fā)安卓App時都會有這樣需求,圖片選擇或拍照選擇,需求實現(xiàn)很簡單,如下: 圖片選擇:調(diào)用系統(tǒng)圖庫進...
    QiuJay閱讀 3,925評論 0 7
  • 內(nèi)容 抽屜菜單 ListView WebView SwitchButton 按鈕 點贊按鈕 進度條 TabLayo...
    小狼W閱讀 1,614評論 0 10
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,264評論 25 707
  • 最近因為遇到了一些事情寂呛,有很多感觸怎诫,所以就想寫一寫,記錄下來贷痪。 今年在我快要過生日的時候父親不幸患肺腺癌幻妓,當時我是...
    散是滿天星閱讀 446評論 3 2
  • 當你長期處于一個圈子,即使你想反抗劫拢,你的反抗也被局限在這個圈子里肉津。 當你的生活被阻塞太多,你本身會失去思考力尚镰,失去自我阀圾。
    中毒成癮閱讀 197評論 0 0