android仿華爲系統相冊之智能相冊開發

一、開發內容簡介

最近課程要求仿照華爲系統相冊做一個android相冊客戶端,我稱之爲智能相冊(智能是指其使用了機器學習的人臉識別、人臉檢測和分類算法)。本着反正實驗報告寫了也是寫了的心態,還有自己在寫的過程中搜索資料的時候發現其實好像有關於相冊列表獲取這部分內容網上講的其實挺少的,雖然我的辦法並不是多麼高明,在這裏還是給大家分享一下,希望能對一些人起到幫助。

二、開發內容要求

智能相冊APP參照華爲系統相冊的樣式和功能,主要分爲三部分內容:
  • 照片:顯示手機存儲的所有圖片
  • 相冊:將手機的所有圖片按不同的相冊分類,點擊各個相冊查看其包含圖片
  • 分類:根據智能算法對手機存儲中的所有圖片進行整理與分類,例如“人像”、“地點”、“事物”等

華爲系統相冊樣式如下:

三、智能相冊效果展示

四、開發過程詳述

(1)頂部導航欄的實現

頂部導航欄借用了github上的一個開源第三方庫wasabeef/awesome-android-ui實現,選取其中的SmartTabLayout控件,注意這裏有andoridx和legacy android support library版本,我選取的是legacy android support library版本,具體實現如下:
  1. build.gradle文件添加依賴
dependencies {
    compile 'com.ogaclejapan.smarttablayout:library:1.7.0@aar'

    //Optional: see how to use the utility.
    compile 'com.ogaclejapan.smarttablayout:utils-v4:1.7.0@aar'

    //Deprecated since 1.7.0
    compile 'com.ogaclejapan.smarttablayout:utils-v13:1.7.0@aar'
} 
  1. activity_main.xml文件添加代碼如下:
<com.ogaclejapan.smarttablayout.SmartTabLayout
        android:id="@+id/viewpagertab"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        app:stl_indicatorAlwaysInCenter="false"
        app:stl_indicatorWithoutPadding="false"
        app:stl_indicatorInFront="true"
        app:stl_indicatorInterpolation="smart"
        app:stl_indicatorGravity="bottom"
        app:stl_indicatorColor="#40C4FF"
        app:stl_indicatorThickness="3dp"
        app:stl_indicatorWidth="auto"
        app:stl_indicatorCornerRadius="2dp"
        app:stl_overlineColor="@color/white"
        app:stl_overlineThickness="0dp"
        app:stl_underlineColor="@color/white"
        app:stl_underlineThickness="1dp"
        app:stl_dividerColor="@color/white"
        app:stl_dividerThickness="1dp"
        app:stl_defaultTabBackground="?attr/selectableItemBackground"
        app:stl_defaultTabTextAllCaps="true"
        app:stl_defaultTabTextColor="#FC000000"
        app:stl_defaultTabTextSize="16sp"
        app:stl_defaultTabTextHorizontalPadding="50dp"
        app:stl_defaultTabTextMinWidth="50dp"
        app:stl_distributeEvenly="true"
        app:stl_clickable="true"
        app:stl_titleOffset="24dp"
        app:stl_drawDecorationAfterTab="false"
        />

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_marginTop="48dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/viewpagertab"
        />

  1. MainActivity.java文件設置Adapter和ViewPager,代碼如下:
FragmentPagerItemAdapter adapter = new FragmentPagerItemAdapter(
                getSupportFragmentManager(), FragmentPagerItems.with(this)
                .add("照片", PageFragment1.class)
                .add("相冊", PageFragment2.class)
                .add("發現", PageFragment3.class)
                .create());

        ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
        viewPager.setAdapter(adapter);

        SmartTabLayout viewPagerTab = (SmartTabLayout) findViewById(R.id.viewpagertab);
        viewPagerTab.setViewPager(viewPager);

到這裏就順利完成了頂部導航欄的實現。


(2)實現照片欄展示手機存儲中的所有圖片

要展示手機中存儲的所有圖片,首先我們需要成功獲取到手機中所有圖片的路徑,通過圖片路徑訪問圖片並把圖片加載出來。這個功能的實現主要分爲以下幾個步驟:
  1. 動態申請讀取存儲權限
public static void verifyStoragePermissions(Activity activity) {
        try {

            //檢測是否有寫的權限
            int permission = ActivityCompat.checkSelfPermission(activity,
                    "android.permission.WRITE_EXTERNAL_STORAGE");
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // 沒有寫的權限,去申請寫的權限,會彈出對話框
                ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
  1. 讀取手機存儲查找所有圖片並存儲圖片路徑以及其他相關信息。
  • 首先,我定義了一個數據類SpacePhoto用於存儲每張圖片的相關信息(路徑,名稱等),SpacePhoto數據類實現了Parcelable類,Parcelable用來從一個組件傳輸高性能數據到另一個組件,在這裏,我們將圖片的URL從相冊的縮略圖界面傳遞至SpacePhotoActivity。SpacePhoto數據類定義如下:
    public class SpacePhoto implements Parcelable {
    
        private String mUrl;
        private String mTitle;
    
        public SpacePhoto(String url, String title) {
            mUrl = url;
            mTitle = title;
        }
    
        protected SpacePhoto(Parcel in) {
            mUrl = in.readString();
            mTitle = in.readString();
        }
    
        public static final Creator<SpacePhoto> CREATOR = new Creator<SpacePhoto>() {
            @Override
            public SpacePhoto createFromParcel(Parcel in) {
                return new SpacePhoto(in);
            }
    
            @Override
            public SpacePhoto[] newArray(int size) {
                return new SpacePhoto[size];
            }
        };
    
        public String getUrl() {
            return mUrl;
        }
    
        public void setUrl(String url) {
            mUrl = url;
        }
    
        public String getTitle() {
            return mTitle;
        }
    
        public void setTitle(String title) {
            mTitle = title;
        }
    
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel parcel, int i) {
            parcel.writeString(mUrl);
            parcel.writeString(mTitle);
        }
    }
    
  • 然後是使用ContentResolver組件查詢手機的所有圖片,代碼如下:
    Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            String[] projImage = { MediaStore.Images.Media._ID
                    , MediaStore.Images.Media.DATA
                    ,MediaStore.Images.Media.SIZE
                    ,MediaStore.Images.Media.DISPLAY_NAME};
            Cursor mCursor = getActivity().getContentResolver().query(mImageUri,
                    projImage,
                    MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?",
                    new String[]{"image/jpeg", "image/png"},
                    MediaStore.Images.Media.DATE_MODIFIED+" desc");
    
            if( mCursor != null )
            {
                while(mCursor.moveToNext()){
                    String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA));
                    String displayName = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));
                    all_photo_set.add(new SpacePhoto(path,displayName)); //將獲取到的路徑加入路徑集合
    
    注意到一個問題,由於相冊的初始頁面就是“照片”或者“分類”,所以每次打開APP的時候必須都能夠順利讀取手機存儲中的圖片,即每次打開APP時,都必須保證已獲取讀取手機存儲的權限。但是這樣就會面臨一個問題,當一部手機第一次安裝此APP時,APP還沒有獲取到讀取手機存儲的權限(權限需要動態獲取並由手機用戶決定是否允許讀取),則此時相冊的佈局是無法加載出來的,會直接閃退。爲解決此問題,在APP中設置一個引導頁,引導頁會在APP第一次被打開時出現(以後便不會再出現),詢問用戶是否給予讀取手機存儲的權限,進而在進入相冊的主頁面。另外,在引導頁還會完成對手機存儲掃描的全過程,獲取到手機中的所有圖片路徑,引導頁代碼如下:
    public class SplashActivity extends AppCompatActivity {
    
    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static String[] PERMISSIONS_STORAGE = {
            "android.permission.READ_EXTERNAL_STORAGE",
            "android.permission.WRITE_EXTERNAL_STORAGE" };
    private MyApplication app;
    private ArrayList<SpacePhoto> all_photo_set = new ArrayList<>(); // 存放所有圖片的路徑
    private ArrayList<album> all_album = new ArrayList<>(); //按系統相冊所屬分開照片
    public static int MODE = Context.MODE_PRIVATE;
    private boolean isFirstIn;
    private SharedPreferences preferences;
    private Button enter_button;
    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        if (getSupportActionBar() != null){  // 去掉標題欄
            getSupportActionBar().hide();
        }
    
        preferences = getSharedPreferences("first_pref", MODE_PRIVATE);
        isFirstIn = preferences.getBoolean("isFirstIn", true);
    
        if(isFirstIn){
            enter_button = findViewById(R.id.enter_button);
            enter_button.setVisibility(View.VISIBLE);
    
            verifyStoragePermissions(SplashActivity.this);
    
            SharedPreferences preferences = getSharedPreferences("first_pref", MODE_PRIVATE);
            SharedPreferences.Editor editor = preferences.edit();
            editor.putBoolean("isFirstIn", false);
            editor.commit();
        }
        else{
            enter_button = findViewById(R.id.enter_button);
            enter_button.setVisibility(View.VISIBLE);
            startActivity(new Intent(this,MainActivity.class));
            finish();
        }
    }
    
    public void search_all_picture() { // 獲取系統中所有圖片的路徑
        Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        String[] projImage = { MediaStore.Images.Media._ID
                , MediaStore.Images.Media.DATA
                ,MediaStore.Images.Media.SIZE
                ,MediaStore.Images.Media.DISPLAY_NAME};
        Cursor mCursor = this.getContentResolver().query(mImageUri,
                projImage,
                MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?",
                new String[]{"image/jpeg", "image/png"},
                MediaStore.Images.Media.DATE_MODIFIED+" desc");
    
        if( mCursor != null )
        {
            while(mCursor.moveToNext()){
                String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA));
                String displayName = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));
                all_photo_set.add(new SpacePhoto(path,displayName)); //將獲取到的路徑加入路徑集合
    
                String dirPath = new File(path).getParentFile().getAbsolutePath(); // 獲取該圖片的父路徑名
    
                int pdf = 0; //判斷此圖片所屬的相冊是否已存在,pdf爲0表示圖片所屬的相冊還不存在
                for(int i = 0; i < all_album.size(); i++){
                    if( all_album.get(i).getDirpath().equals(dirPath)){
                        pdf = 1;
                        all_album.get(i).add(new SpacePhoto(path,displayName));
                    }
                }
                if( pdf == 0){
                    ArrayList<SpacePhoto> new_list = new ArrayList<>();
                    new_list.add(new SpacePhoto(path,displayName));
                    String Str[] = dirPath.split("/");
                    String album_name = Str[Str.length - 1];
                    all_album.add(new album(new_list,dirPath,album_name));
                }
    
            }
    
        }
    
    }
    public static void verifyStoragePermissions(Activity activity) {
        try {
    
            //檢測是否有寫的權限
            int permission = ActivityCompat.checkSelfPermission(activity,
                    "android.permission.WRITE_EXTERNAL_STORAGE");
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // 沒有寫的權限,去申請寫的權限,會彈出對話框
                ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void gotoMainAct(View view){
        search_all_picture();
        app = (MyApplication)getMyApplication();
        app.set_all_album(all_album);
        app.set_all_photo_set(all_photo_set);
        startActivity(new Intent(this,MainActivity.class));
        finish();
    }
    }
    
    這裏我使用SharedPreferences來處理判斷用戶是否第一次打開此APP的邏輯。
  1. 成功獲取圖片的路徑集合後,根據路徑加載圖片將所有圖片展示出來
  • 實現圖片展示列表

    使用android提供的初始接口來根據圖片url加載圖片是比較麻煩的,首先要根據url獲取圖片的真實路徑,再根據真實路徑獲取bitmap數據類型的圖片,最後再通過imageview控件展示出來;其次還有圖片加載的性能問題,因爲圖片展示列表要求快速加載系統中的所有圖片。針對以上問題,我使用了一個第三方的Android開源庫Glide。Glide是一個快速高效的Android圖片加載庫,注重於平滑的滾動。Glide提供了易用的API,高性能、可擴展的圖片解碼管道(decode pipeline),以及自動的資源池技術。Glide 支持拉取,解碼和展示視頻快照,圖片,和GIF動畫。Glide的具體使用方法如下:
    • 在build.gradle文件中添加以下依賴:
    // Glide
    compile 'com.github.bumptech.glide:glide:3.7.0'
    
    • 通過圖片url加載圖片
    Glide.with(mContext) //傳遞上下文
                .load(spacePhoto.getUrl()) // 目錄路徑或者URI或者URL
                .centerCrop() // 圖片有可能被裁剪
                .placeholder(R.drawable.error) //一個本地APP資源id,在圖片被加載前作爲佔位的圖片
                .into(imageView); // 要放置圖片的目標imageView控件
    
    使用Recyclerview來製作圖片展示列表,Recyclerview具體用法如下:
    • 在build.gradle文件中添加以下依賴:
    // Recyclerview
    compile 'com.android.support:recyclerview-v7:25.1.1'
    
    • 自定義ImageGalleryAdapter,要求繼承RecyclerView.Adapter類,具體代碼如下:
    class ImageGalleryAdapter extends RecyclerView.Adapter<ImageGalleryAdapter.MyViewHolder>  {
    
    private ArrayList<SpacePhoto> mSpacePhotos;
    private Context mContext;
    
    @Override
    public ImageGalleryAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    
        Context context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);
        View photoView = inflater.inflate(R.layout.item_photo, parent, false);
        ImageGalleryAdapter.MyViewHolder viewHolder = new ImageGalleryAdapter.MyViewHolder(photoView);
        return viewHolder;
    }
    
    @Override
    public void onBindViewHolder(ImageGalleryAdapter.MyViewHolder holder, int position) {
    
        SpacePhoto spacePhoto = mSpacePhotos.get(position);
        ImageView imageView = holder.mPhotoImageView;
        Glide.with(mContext) //傳遞上下文
                .load(spacePhoto.getUrl()) // 目錄路徑或者URI或者URL
                .centerCrop() // 圖片有可能被裁剪
                .placeholder(R.drawable.error) //一個本地APP資源id,在圖片被加載前作爲佔位的圖片
                .into(imageView); // 要放置圖片的目標imageView控件
    }
    
    @Override
    public int getItemCount() {
        return (mSpacePhotos.size());
    }
    
    public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    
        public ImageView mPhotoImageView;
        public MyViewHolder(View itemView) {
            super(itemView);
            mPhotoImageView = (ImageView) itemView.findViewById(R.id.iv_photo);
            itemView.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View view) {
    
            int position = getAdapterPosition();
            if(position != RecyclerView.NO_POSITION) {
                SpacePhoto spacePhoto = mSpacePhotos.get(position);
                String url = spacePhoto.getUrl();
                Intent intent = new Intent(mContext, SpacePhotoActivity.class);
                Bundle bundle = new Bundle();
                bundle.putString("url",url);
                intent.putExtras(bundle);
                mContext.startActivity(intent);
            }
        }
    }
    
    public ImageGalleryAdapter(Context context, ArrayList<SpacePhoto> spacePhotos) {
        mSpacePhotos = new ArrayList<>();
        mContext = context;
        mSpacePhotos = spacePhotos;
    }
    }
    
    在fragement1顯示圖片展示列表,需要在PageFragment1.java文件的onCreateView函數返回一個View,該View加載的是fragment_page1 Laytout;並且,需要在onCreateView函數裏設置Recyclerview的Adapter,具體代碼如下:
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, // 系統會在Fragment首次繪製其用戶界面時調用此方法
                             @Nullable Bundle savedInstanceState) {
    
        View root = inflater.inflate(R.layout.fragment_page1,container,false);
    
        search_all_picture();
        app = (MyApplication)getMyApplication();
        app.set_all_photo_set(all_photo_set); //放到Application中
        app.set_all_album(all_album);
    
        RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), 2);
        RecyclerView recyclerView = (RecyclerView) root.findViewById(R.id.rv_images_1);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(layoutManager);
    
        ImageGalleryAdapter adapter = new ImageGalleryAdapter(getActivity(), all_photo_set);
        //調用這個函數的時候SpacePhoto並不是空的
        recyclerView.setAdapter(adapter);
        return root;
    }
    
    fragment_page1.xml佈局文件如下:
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_images_1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    
    </android.support.design.widget.CoordinatorLayout>
    
  • 單擊圖片進入圖片展示頁面

    我使用SpacePhotoActivity用於實現圖片展示頁面,在ImageGalleryAdapter類中定義內部類MyViewHolder實現View.OnClickListener,並重載onclick函數如下:
    @Override
        public void onClick(View view) {
    
            int position = getAdapterPosition();
            if(position != RecyclerView.NO_POSITION) {
                SpacePhoto spacePhoto = mSpacePhotos.get(position);
                String url = spacePhoto.getUrl();
                Intent intent = new Intent(mContext, SpacePhotoActivity.class);
                Bundle bundle = new Bundle();
                bundle.putString("url",url);
                intent.putExtras(bundle);
                mContext.startActivity(intent);
            }
        }
    
    每次在圖片點擊列表單擊某個圖片,則跳轉到SpacePhotoActivity並展示被點擊的圖片,這裏使用Intent中傳遞的是被點擊圖片的url,在SpacePhotoActivity中根據傳遞過來的圖片url加載圖片,代碼如下:
        mImageView = (ImageView) findViewById(R.id.image);
            Intent intent = getIntent();
            Bundle bundle = intent.getExtras();
            String url = bundle.getString("url");
        Glide.with(this)
                .load(url)
                .asBitmap()
                .error(R.drawable.error)
                .into(mImageView);
                
    

到這裏我們完成了讀取手機中的所有照片並將其展示出來的功能。


(3)將手機的所有圖片按不同的相冊分類,點擊各個相冊查看其包含圖片

  • 獲取相冊列表沒有找到很好的方法,我們都知道獲取手機圖片要用到ContentResolver類,但是通過這個類卻沒有辦法獲取到相冊。所以我採用了一種曲線救國的辦法:在獲取每張圖片的路徑的時候,同時獲取該圖片的父路徑,然後把父路徑相同的所有圖片歸結爲一個相冊內的圖片,雖然做法有點笨,但是效果還可以,獲取父路徑並按其分類圖片的代碼如下:

    String dirPath = new File(path).getParentFile().getAbsolutePath(); // 獲取該圖片的父路徑名
    int pdf = 0; //判斷此圖片所屬的相冊是否已存在,pdf爲0表示圖片所屬的相冊還不存在
    for(int i = 0; i < all_album.size(); i++){
        if( all_album.get(i).getDirpath().equals(dirPath)){
            pdf = 1;
            all_album.get(i).add(new SpacePhoto(path,displayName));
        }
    }
    if( pdf == 0){
        ArrayList<SpacePhoto> new_list = new ArrayList<>();
        new_list.add(new SpacePhoto(path,displayName));
        String Str[] = dirPath.split("/");
        String album_name = Str[Str.length - 1];
        all_album.add(new album(new_list,dirPath,album_name));
    }
    

    爲了更好地表示每個相冊,我寫了一個相冊數據類album,包含三個屬性:

    • 一個用來存放該相冊所包含的圖片的Arraylist;
    • 相冊的名稱;
    • 該相冊內所有圖片的父路徑;

    album數據類的具體實現如下:

    public class album {
    private ArrayList<SpacePhoto> photo_set;
    private String album_name;
    private String dirpath;
    
    public album(ArrayList<SpacePhoto> list,String path,String str){
        photo_set = list;
        dirpath = path;
        album_name = str;
    }
    public void add(SpacePhoto item){
        photo_set.add(item);
    }
    
    public String getAlbum_name(){
        return album_name;
    }
    
    public String getDirpath(){
        return dirpath;
    }
    
    public int size(){
        return photo_set.size();
    }
    
    public ArrayList<SpacePhoto> getPhotoList(){
        return photo_set;
    }
    }
    

    注意,album數據類的album_name屬性的值是通過該相冊中所有圖片的公共父路徑來獲得的。仔細觀察路徑的表示方式,我發現獲取相冊名稱可通過使用String.spilt函數分解父路徑字符串,獲取最後一個’/'之後的子串來表示。另外,我們還需要一個Arraylist來存儲所有的album。

  • 相冊界面要求展示出出相冊列表,所以將圖片按照相冊分類好之後,還需要通過一個Listview來展示出相冊列表,ListView的實現比較簡單,這裏只貼一下代碼:

    public class listViewAdapter extends BaseAdapter
    {
    
        private ArrayList<album> list;
        private Context context;
    
    
        public listViewAdapter(ArrayList<album>list, Context context)
        {
            this.list = list;
            this.context = context;
        }
    
    
        @Override
        public int getCount() {
            if (list == null) {
                return 0;
            }
            return list.size();
        }
    
        @Override
        public long getItemId(int i) {
            return i;
        }
    
        @Override
        public Object getItem(int i) {
            if (list == null) {
                return null;
            }
            return list.get(i);
        }
        @Override
        public View getView(int i, View view, ViewGroup viewGroup) {
            // 通過inflate的方法加載佈局,context需要在使用這個Adapter的Activity中傳入。
            if( view == null )
            {
                view = LayoutInflater.from(context).inflate(R.layout.album, null);
            }
            ImageView image = view.findViewById(R.id.cover);
            TextView album_name = view.findViewById(R.id.album_name);
            TextView picture_num = view.findViewById(R.id.picture_num);
    
            Glide.with(context) //傳遞上下文
                    .load(list.get(i).getPhotoList().get(0).getUrl()) // 目錄路徑或者URI或者URL
                    .centerCrop() // 圖片有可能被裁剪
                    .placeholder(R.drawable.error) //一個本地APP資源id,在圖片被加載前作爲佔位的圖片
                    .into(image); // 要放置圖片的目標imageView控件
            
            picture_num.setText(list.get(i).size() + "張圖片");
            album_name.setText(list.get(i).getAlbum_name());
    
            return view; // 將這個處理好的view返回
        }
    }
    

    fragment_page2佈局文件如下:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
    </ListView>
    
    </android.support.design.widget.CoordinatorLayout>
    

    PageFragment2.java中爲listview設置Adapter如下:

    albumAdapter = new listViewAdapter(app.get_all_album(),getActivity());
        listView = root.findViewById(R.id.listview);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Intent intent = new Intent(getActivity(),album_show_page.class);
                Bundle bundle = new Bundle();
                bundle.putInt("index",position);
                intent.putExtras(bundle);
                startActivity(intent);
            }
        });
        listView.setAdapter(albumAdapter);
    

    點擊listview中的相冊可跳轉到album_show_page,顯示該相冊所包含的所有圖片,圖片列表實現的代碼只需複用fragment照片的即可。

到這爲止我們也順利實現了相冊欄。


(4)關於照片fragment和相冊fragment實現內容的補充——重寫Application類

注意到照片欄和相冊欄的實現都需要首先獲取到手機存儲中的所有圖片,若在照片欄和相冊欄分別執行一次查找手機存儲獲取圖片路徑的操作會顯得非常冗餘;而且當在相冊列表中每次點擊某個相冊時,若採用Intent將整個相冊中圖片的url作爲參數傳遞,則效率會非常低下。爲了只進行一次查找手機存儲的操作,很自然地想到可以將圖片路徑的集合設置爲全局變量,這樣只要在整個項目的某一處進行了查找手機存儲獲取了所有圖片路徑的操作,則之後就不再需要重複此操作。在android裏聲明全局變量可通過重寫Application類來實現。Application和Activity,Service一樣是android框架的一個系統組件,當android程序啓動時系統會創建一個application對象,用來存儲系統的一些信息。通常我們是不需要指定一個Application的,這時系統會自動幫我們創建。打開每一個應用程序的manifest文件,可以看到activity都是包含在application標籤之中的。android系統會爲每個程序運行時創建一個Application類的對象且僅創建一個,所以Application是單例 (singleton)模式的一個類.且application對象的生命週期是整個程序中最長的,它的生命週期就等於這個程序的生命週期。因爲它是全局的單例的,所以在不同的Activity,Service中獲得的對象都是同一個對象。因此在android中我們可以避免使用靜態變量來存儲長久保存的值,而用Application。爲了更好的利用Application的這一特性,比如我們需要Application來保存一些靜態值,需要自定義繼承於Application的類,然後在這個類中定義一個變量來保存。在默認情況下應用系統會自動生成Application 對象,但是如果我們自定義了Application,那就需要告知系統,實例化的時候,是實例化我們自定義的,而非默認的。爲了讓系統實例化的時候找到,我們必須在manifest中修改application標籤屬性,代碼如下:
<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">
        android:name=".MyApplication"
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".SpacePhotoActivity" />
        <activity android:name=".album_show_page"></activity>
    </application>

其中,最關鍵的是這一句:android:name=".MyApplication"。

重寫的MyApplication類如下:

public class MyApplication extends Application{
    private static MyApplication instance;
    private static ArrayList<SpacePhoto> all_photo_set = new ArrayList<>(); // 存放所有圖片的路徑
    private static ArrayList<album> all_album = new ArrayList<>(); //按系統相冊所屬分開照片

    // 獲取Application
    public static Context getMyApplication() {
        return instance;
    }

    public static ArrayList<SpacePhoto> get_all_photo_set(){
        return all_photo_set;
    }

    public static ArrayList<album> get_all_album(){
        return all_album;
    }

    public static void set_all_photo_set(ArrayList<SpacePhoto> copy){
        all_photo_set = copy;
    }

    public static void set_all_album(ArrayList<album> copy){
        all_album = copy;
    }
}

由以上代碼可知,MyApplication類中既存放了所有的路徑集合,也存放了按照相冊分好的路徑集合,在任何需要這些數據的地方,只要根據相應的函數進行請求即可。在需要獲取全局對象的地方,通過以下代碼獲取application單例:

app = (MyApplication)getMyApplication();

在相冊列表界面,點擊相冊跳轉到該相冊的圖片展示列表,其中intent傳遞的不是該相冊所有圖片的url,而是該album在ArrayList中的下標,在show_album_page Activity中,只要根據下標進行請求數據即可。


關於分類欄的分類算法,由於涉及科研隱私,在這裏就不說了,文章很長,謝謝閱讀。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章