一個(gè)簡(jiǎn)單的Android相冊(cè)App

本文文字稍多,源碼下載地址在最后。

01 效果圖

主頁.png
選擇相冊(cè).png
圖片預(yù)覽.png

02 工程目錄

Project.png

03 用到的技術(shù)點(diǎn)

LoaderManager惰拱、Glide圖片加載框架镜撩、ButterKnife注解、easypermissions愈腾、RecyclerView、PopupWindow、ViewPager

04 一小部分關(guān)鍵代碼

圖片預(yù)覽的ViewPager
public class PreviewViewPager extends ViewPager {
    private boolean mScrolling;
    private float touchDownY;
    private int mTouchSlop;

    public PreviewViewPager(Context context) {
        this(context, null);
    }

    public PreviewViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    }

    // 由于ViewPager Adapter的Item都有添加點(diǎn)擊事件否纬,為了避免上下滑動(dòng)和點(diǎn)擊事件的沖突做如下處理。
    // 上下滑動(dòng)時(shí)攔截事件蛋褥,只有真正的點(diǎn)擊時(shí)才執(zhí)行Item的點(diǎn)擊事件临燃。
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean isBeingDragged = super.onInterceptTouchEvent(ev);

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchDownY = ev.getY();
                mScrolling = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (Math.abs(touchDownY - ev.getY()) >= mTouchSlop) {
                    mScrolling = true;
                }
                else{
                    mScrolling = false;
                }
                break;

            case MotionEvent.ACTION_UP:
                mScrolling = false;
                break;
        }
        return isBeingDragged ? isBeingDragged : mScrolling;
    }
}
相冊(cè)主頁的Fragment
public class AlbumFragment extends BaseFragment implements ImageLoaderListener, View.OnClickListener, BaseRecyclerAdapter.OnItemClickListener{
    @BindView(R.id.rv_image) RecyclerView mContentView;
    @BindView(R.id.btn_title_select) Button mSelectFolderView;
    @BindView(R.id.iv_title_select) ImageView mSelectFolderIcon;
    @BindView(R.id.toolbar) View mToolbar;

    private LoaderListener mCursorLoader;
    private ImageFolderAdapter mImageFolderAdapter;
    private ImageAdapter mImageAdapter;
    private ImageFolderPopupWindow mFolderPopupWindow;
    private String[] mImageSources;

    @Override
    protected int getLayoutId() {
        return R.layout.fragment_select_image;
    }

    @Override
    protected void initWidget(View root) {
        super.initWidget(root);
        mContentView.setLayoutManager(new GridLayoutManager(getActivity(), 4));
        mContentView.addItemDecoration(new SpaceGridItemDecoration((int) DeviceUtil.dipToPx(getResources(), 1)));
        mImageAdapter = new ImageAdapter(getContext(), this);
        mImageFolderAdapter = new ImageFolderAdapter(getContext(), this);
        mContentView.setAdapter(mImageAdapter);
        mContentView.setItemAnimator(null);
        mImageAdapter.setOnItemClickListener(this);
    }

    @Override
    protected void initData() {
        super.initData();
        mCursorLoader = new LoaderListener();
        getLoaderManager().initLoader(0, null, mCursorLoader);
    }

    @Override
    public void displayImage(ImageView iv, String path) {
        // Load image
        getImgLoader().load(path)
                .asBitmap()
                .centerCrop()
                .error(R.mipmap.ic_split_graph)
                .into(iv);
    }

    @OnClick({R.id.btn_title_select})
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_title_select:
                showPopupFolderList();
                break;
        }
    }

    /**
     * 創(chuàng)建彈出的相冊(cè)
     */
    private void showPopupFolderList() {
        if (mFolderPopupWindow == null) {
            ImageFolderPopupWindow popupWindow = new ImageFolderPopupWindow(getContext(), new ImageFolderPopupWindow.Callback() {
                @Override
                public void onSelect(ImageFolder imageFolder) {
                    addImagesToAdapter(imageFolder.getImages());
                }

                @Override
                public void onDismiss() {
                    mSelectFolderIcon.setImageResource(R.mipmap.ic_arrow_bottom);
                }

                @Override
                public void onShow() {
                    mSelectFolderIcon.setImageResource(R.mipmap.ic_arrow_top);
                }
            });
            popupWindow.setAdapter(mImageFolderAdapter);
            mFolderPopupWindow = popupWindow;
        }
        mFolderPopupWindow.showAsDropDown(mToolbar);
    }

    @Override
    public void onItemClick(int position) {
        ImageGalleryActivity.show(getContext(), mImageSources, position);
    }

    private class LoaderListener implements LoaderManager.LoaderCallbacks<Cursor> {
        private final String[] IMAGE_PROJECTION = {
                MediaStore.Images.Media.DATA,
                MediaStore.Images.Media.DISPLAY_NAME,
                MediaStore.Images.Media.DATE_ADDED,
                MediaStore.Images.Media._ID,
                MediaStore.Images.Media.MINI_THUMB_MAGIC,
                MediaStore.Images.Media.BUCKET_DISPLAY_NAME};

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            if (id == 0) {
                //數(shù)據(jù)庫光標(biāo)加載器
                return new CursorLoader(getContext(),
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,
                        null, null, IMAGE_PROJECTION[2] + " DESC");
            }
            return null;
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            if (data != null) {
                final ArrayList<Image> images = new ArrayList<>();
                final List<ImageFolder> imageFolders = new ArrayList<>();

                final ImageFolder defaultFolder = new ImageFolder();
                defaultFolder.setName("全部照片");
                defaultFolder.setPath("");
                imageFolders.add(defaultFolder);

                int count = data.getCount();
                if (count > 0) {
                    data.moveToFirst();
                    do {
                        String path = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[0]));
                        String name = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[1]));
                        long dateTime = data.getLong(data.getColumnIndexOrThrow(IMAGE_PROJECTION[2]));
                        int id = data.getInt(data.getColumnIndexOrThrow(IMAGE_PROJECTION[3]));
                        String thumbPath = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[4]));
                        String bucket = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[5]));

                        Image image = new Image();
                        image.setPath(path);
                        image.setName(name);
                        image.setDate(dateTime);
                        image.setId(id);
                        image.setThumbPath(thumbPath);
                        image.setFolderName(bucket);

                        images.add(image);

                        File imageFile = new File(path);
                        File folderFile = imageFile.getParentFile();
                        ImageFolder folder = new ImageFolder();
                        folder.setName(folderFile.getName());
                        folder.setPath(folderFile.getAbsolutePath());
                        if (!imageFolders.contains(folder)) {
                            folder.getImages().add(image);
                            folder.setAlbumPath(image.getPath());//默認(rèn)相冊(cè)封面
                            imageFolders.add(folder);
                        } else {
                            // 更新
                            ImageFolder f = imageFolders.get(imageFolders.indexOf(folder));
                            f.getImages().add(image);
                        }
                    } while (data.moveToNext());
                }

                addImagesToAdapter(images);
                defaultFolder.getImages().addAll(images);
                defaultFolder.setAlbumPath(images.size() > 0 ? images.get(0).getPath() : null);
                mImageFolderAdapter.resetItem(imageFolders);
            }
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
        }
    }

    private void addImagesToAdapter(ArrayList<Image> images) {
        mImageAdapter.resetItem(images);
        mImageSources = toArray(images);
    }

    private static String[] toArray(List<Image> images) {
        if (images == null)
            return null;
        int len = images.size();
        if (len == 0)
            return null;

        String[] strings = new String[len];
        int i = 0;
        for (Image image : images) {
            strings[i] = image.getPath();
            i++;
        }
        return strings;
    }
}
圖片文件夾選擇(PopupWindow)
public class ImageFolderPopupWindow extends PopupWindow implements View.OnAttachStateChangeListener, BaseRecyclerAdapter.OnItemClickListener{
    private ImageFolderAdapter mAdapter;
    private RecyclerView mFolderView;

    private Callback mCallback;

    public ImageFolderPopupWindow(Context context, Callback callback) {
        super(LayoutInflater.from(context).inflate(R.layout.popup_window_folder, null),
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

        mCallback = callback;

        // init
        setAnimationStyle(R.style.popup_anim_style_alpha);
        setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        setOutsideTouchable(true);
        setFocusable(true);

        // content
        View content = getContentView();
        content.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

        content.addOnAttachStateChangeListener(this);

        mFolderView = (RecyclerView) content.findViewById(R.id.rv_popup_folder);
        mFolderView.setLayoutManager(new LinearLayoutManager(context));
    }

    public void setAdapter(ImageFolderAdapter adapter) {
        this.mAdapter = adapter;
        mFolderView.setAdapter(adapter);
        mAdapter.setOnItemClickListener(this);
    }

    @Override
    public void onItemClick(int position) {
        if (mCallback != null) mCallback.onSelect(mAdapter.getItem(position));
        dismiss();
    }

    @Override
    public void onViewAttachedToWindow(View v) {
        if(mCallback != null) mCallback.onShow();
    }

    @Override
    public void onViewDetachedFromWindow(View v) {
        if(mCallback != null) mCallback.onDismiss();
    }

    public interface Callback {
        void onSelect(ImageFolder imageFolder);
        void onDismiss();
        void onShow();
    }
}

05 源碼

源碼下載地址

06 PhotoAlbum apk

PhotoAlbum apk下載地址烙心。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末膜廊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子淫茵,更是在濱河造成了極大的恐慌爪瓜,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痘昌,死亡現(xiàn)場(chǎng)離奇詭異钥勋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)辆苔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門算灸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人驻啤,你說我怎么就攤上這事菲驴。” “怎么了骑冗?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵赊瞬,是天一觀的道長(zhǎng)先煎。 經(jīng)常有香客問我,道長(zhǎng)巧涧,這世上最難降的妖魔是什么薯蝎? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮谤绳,結(jié)果婚禮上占锯,老公的妹妹穿的比我還像新娘。我一直安慰自己缩筛,他們只是感情好消略,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瞎抛,像睡著了一般艺演。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上桐臊,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天胎撤,我揣著相機(jī)與錄音,去河邊找鬼断凶。 笑死哩照,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的懒浮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼识藤,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼砚著!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起痴昧,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤稽穆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后赶撰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舌镶,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年豪娜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了餐胀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瘤载,死狀恐怖否灾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鸣奔,我是刑警寧澤墨技,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布惩阶,位于F島的核電站,受9級(jí)特大地震影響扣汪,放射性物質(zhì)發(fā)生泄漏断楷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一崭别、第九天 我趴在偏房一處隱蔽的房頂上張望冬筒。 院中可真熱鬧,春花似錦紊遵、人聲如沸账千。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匀奏。三九已至,卻和暖如春学搜,著一層夾襖步出監(jiān)牢的瞬間娃善,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工瑞佩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留聚磺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓炬丸,卻偏偏與公主長(zhǎng)得像瘫寝,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子稠炬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,117評(píng)論 25 707
  • 最近焕阿,總是煩躁,煩躁到不能心靜首启,坐下就一身汗暮屡,看書看十分鐘就煩……我今年大三了,是學(xué)生毅桃,在這大學(xué)儼然已過半的行程中...
    高飛的Dream閱讀 131評(píng)論 0 0
  • 朋友都說褒纲,第一眼見你的時(shí)候,覺得你是個(gè)安靜不愛說話的人钥飞,但是后來發(fā)現(xiàn)自己當(dāng)時(shí)的想法好天真莺掠。→_→ 我...
    懶到不想想昵稱閱讀 309評(píng)論 0 0
  • 鬧鐘又無情地響了 一次 兩次 三次 …… 鬧鐘盡職盡責(zé) 記錄著 提醒著 重申著 也催促著 時(shí)間流逝 抽打著我們
    深淺之末閱讀 283評(píng)論 0 1