Android練手小項目(KTReader)基于mvp架構(七)

上路傳送眼:
Android練手小項目(KTReader)基于mvp架構(六)

Github地址: https://github.com/yiuhet/KTReader

上篇文章我們完成了項目的收藏和歷史功能,
這篇我們將要完成該項目的最后的功能設置頁和關于頁。

先看看完成圖:

效果圖

我們想要在設置和關于界面完成的功能有:

  • 設置夜間模式
  • 主題色的選擇
  • 清空應用緩存
  • 查看項目源碼
  • 分享好友應用

所用到的知識點有:

  • PreferenceFragment
  • SharedPreferences
  • File的刪除
  • 代碼中設置theme
  • 剪切板

可完善和加強的內容或功能有:

  • 檢查更新
  • 關于UI界面美化

ps:由于這里的功能并不復雜,所以我就沒有按照mvp的架構來寫,直接把業務邏輯寫在了view層。(偷懶一波,畢竟期末事情太多,復習考試,課程設計,還有找實習工作/(ㄒoㄒ)/~~)

1. 設置界面

如果我們自己寫設置的界面,還要保存相關的設置,雖然也不是很復雜,但是官方提供了更好的類:PreferenceFragment
我們寫個fragment繼承自PreferenceFragment然后在xml文件中寫布局就好了,系統會自動幫我們保存里面的偏好設置(通過SharedPreferences)。
下面簡述一下Preference的知識點(之后我會寫一篇詳解。):

XML

  • PreferenceScreen
    根節點,若一個xml文件中有嵌套的PreferenceScreen,點擊后將通過另一屏來顯示它包含的內容。

  • PreferenceCategory
    用來分組的東西,可以讓布局更有層次感。
    組件的通用屬性有

  • android:key 唯一標識id

  • android:defaultValue 默認值

  • android:title 主標題

  • android:summary 副標題
    用到的基本組件

  • ListPreference
    基本組件之一,點擊彈出列表對話框。

  • android:dialogTitle
    對話框標題

  • android:entries
    列表顯示的文本()

  • android:entryValues
    實際系統保存的值,和entries一一對應

  • SwitchPreference
    基本組件之一,有on,off兩種狀態。

  • Preference

PreferenceFragment

我們可以通過相關的監聽接口來寫對其需要的功能:

  • onPreferenceClick(Preference preference)
    點擊事件發生,回調該方法
  • onPreferenceChange(Preference preference, Object newValue)
    值改變,回調該方法

我們可以通過以下方式在其他activity中得到Preference保存的值:

SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mPrefs.getString(key, defaultValue);

OK,知道了這些,要寫一個設置界面就很簡單了。
1.要先在res文件下建個xml文件夾(如果沒有),寫xml文件。
2.建立SettingsFragment繼承自PreferenceFragment,
3.在回調方法里寫具體操作。
4.創建單例,里面實現獲取Preference的保存值。
res.xml.preferences:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:title="設置">

<PreferenceCategory
android:title="基本設置">
<SwitchPreference
android:title="夜間模式"
android:summary="夜間保護你的眼睛"
android:key="settings_safe"/>
<ListPreference
android:title="主題色"
android:key="settings_theme"
android:entries="@array/theme_entities"
android:entryValues="@array/theme_values"
android:defaultValue="@string/default_theme"
/>
<Preference
android:title="清空緩存"
android:key="settings_cache" />
</PreferenceCategory>

<PreferenceCategory
android:title="其他設置">
<Preference
android:title="檢查更新"
android:summary="當前版本: 1.0"
android:key="settings_check"/>
<Preference
android:title="查看源碼"
android:summary="給開發者github上點個Star"
android:key="settings_look"/>
</PreferenceCategory>
</PreferenceScreen>

數組文件為:

<resources>
<string-array name="theme_entities">
<item>楞頭青</item>
<item>少女粉</item>
<item>畫韓紅</item>
<item>原諒綠</item>
<item>基佬紫</item>
</string-array>
<string-array name="theme_values">
<item>indigo</item>
<item>pink</item>
<item>red</item>
<item>green</item>
<item>purple</item>
</string-array>

<string name="default_theme">indigo</string>
<string name="default_lr">right</string>
</resources>

出來的效果是這樣的:

image.png

嗯 還不錯。

SettingsFragment

下面開始寫具體業務邏輯:
官方推薦的是使用fragment繼承自PreferenceFragment而不是繼承PreferenceActivity的activity來呈現界面和處理業務。所以我們跟著官方走使用PreferenceFragment。我們要在onCreate中添加這樣一句話來加載xml文件:

addPreferencesFromResource(R.xml.preferences);

然后我們要初始化一些數據,寫個初始化方法:

private void initPer() {
        mOptionBlack = (SwitchPreference) findPreference("settings_safe");
        mOptionTheme = (ListPreference) findPreference("settings_theme");
        mOptionCache = findPreference("settings_cache");
        mOptionCheck = findPreference("settings_check");
        mOptionLook = findPreference("settings_look");

        mOptionBlack.setOnPreferenceChangeListener(this);
        mOptionTheme.setOnPreferenceChangeListener(this);
        mOptionCache.setOnPreferenceClickListener(this);
        mOptionCheck.setOnPreferenceClickListener(this);
        mOptionLook.setOnPreferenceClickListener(this);


        mOptionTheme.setSummary(String.format("當前主題: %s",
                mOptionTheme.getEntry() == null ? "愣頭青" : mOptionTheme.getEntry()));

        mOptionCache.setSummary(FileSizeUtil.getAutoFileOrFilesSize(MyApplication.getAppCacheDir() + "/KTReaderCache"));
    }

在這個方法里,我們創建了對應xml文件中的控件對象,通過設置的key。
然后給它們設置了監聽器。并對副標題動態顯示。

下面就是在回調里寫對它們的監聽處理:
首先是當主題設置被改變時,我們要更改副標題,并且彈出提示框,提示用戶重啟以使設置生效。

@Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        if (preference == mOptionTheme) {
           // mOptionTheme.setSummary("當前主題: " + mOptionTheme.getEntry());
            if (newValue.equals("indigo")) {
                mOptionTheme.setSummary("當前主題: 愣頭青");
            } else if (newValue.equals("pink")) {
                mOptionTheme.setSummary("當前主題: 少女粉");
            }else if (newValue.equals("red")) {
                mOptionTheme.setSummary("當前主題: 畫韓紅");
            }else if (newValue.equals("green")) {
                mOptionTheme.setSummary("當前主題: 原諒綠");
            }else if (newValue.equals("purple")) {
                mOptionTheme.setSummary("當前主題: 基佬紫");
            }
        }
        Snackbar.make(getView(),"主題切換成" +
                "功,重啟應用后生效",Snackbar.LENGTH_INDEFINITE)
                .setAction("重啟", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Intent intent = new Intent(getActivity(), MainActivity.class);
                        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                        getActivity().startActivity(intent);
                        getActivity().finish();
                    }
                })
                .show();
        return true;
    }

之后我們對清除緩存的監聽處理為彈出對話框再次詢問是否清除,防止誤操作,然后當用戶選擇清除時,刪除緩存文件。對查看源碼處理為跳轉到項目github網址:

@Override
    public boolean onPreferenceClick(Preference preference) {
        if (preference == mOptionCache) {
            new AlertDialog.Builder(getActivity())
                    .setTitle("提示")
                    .setMessage("是否清除緩存?")
                    .setPositiveButton("清除", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            clearCache();
                        }
                    })
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {

                        }
                    })
                    .show();
        } else if (preference == mOptionCheck) {
            Snackbar.make(getView(), "已是最新版本", Snackbar.LENGTH_SHORT).show();
        } else if (preference == mOptionLook) {
            goToHtml("https://github.com/yiuhet/KTReader");
        }
        return true;
    }

完整的SettingsFragment代碼如下
ui.fragment.SettingsFragment

public class SettingsFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener {

    SwitchPreference mOptionBlack ;
    ListPreference mOptionTheme;
    Preference mOptionCache;
    Preference mOptionCheck;
    Preference mOptionLook;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        initPer();
    }

    private void initPer() {
        mOptionBlack = (SwitchPreference) findPreference("settings_safe");
        mOptionTheme = (ListPreference) findPreference("settings_theme");
        mOptionCache = findPreference("settings_cache");
        mOptionCheck = findPreference("settings_check");
        mOptionLook = findPreference("settings_look");

        mOptionBlack.setOnPreferenceChangeListener(this);
        mOptionTheme.setOnPreferenceChangeListener(this);
        mOptionCache.setOnPreferenceClickListener(this);
        mOptionCheck.setOnPreferenceClickListener(this);
        mOptionLook.setOnPreferenceClickListener(this);


        mOptionTheme.setSummary(String.format("當前主題: %s",
                mOptionTheme.getEntry() == null ? "愣頭青" : mOptionTheme.getEntry()));

        mOptionCache.setSummary(FileSizeUtil.getAutoFileOrFilesSize(MyApplication.getAppCacheDir() + "/KTReaderCache"));
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        if (preference == mOptionTheme) {
           // mOptionTheme.setSummary("當前主題: " + mOptionTheme.getEntry());
            if (newValue.equals("indigo")) {
                mOptionTheme.setSummary("當前主題: 愣頭青");
            } else if (newValue.equals("pink")) {
                mOptionTheme.setSummary("當前主題: 少女粉");
            }else if (newValue.equals("red")) {
                mOptionTheme.setSummary("當前主題: 畫韓紅");
            }else if (newValue.equals("green")) {
                mOptionTheme.setSummary("當前主題: 原諒綠");
            }else if (newValue.equals("purple")) {
                mOptionTheme.setSummary("當前主題: 基佬紫");
            }
        }
        Snackbar.make(getView(),"主題切換成" +
                "功,重啟應用后生效",Snackbar.LENGTH_INDEFINITE)
                .setAction("重啟", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Intent intent = new Intent(getActivity(), MainActivity.class);
                        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                        getActivity().startActivity(intent);
                        getActivity().finish();
                    }
                })
                .show();
        return true;
    }

    @Override
    public boolean onPreferenceClick(Preference preference) {
        if (preference == mOptionCache) {
            new AlertDialog.Builder(getActivity())
                    .setTitle("提示")
                    .setMessage("是否清除緩存?")
                    .setPositiveButton("清除", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            clearCache();
                        }
                    })
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {

                        }
                    })
                    .show();
        } else if (preference == mOptionCheck) {
            Snackbar.make(getView(), "已是最新版本", Snackbar.LENGTH_SHORT).show();
        } else if (preference == mOptionLook) {
            goToHtml("https://github.com/yiuhet/KTReader");
        }
        return true;
    }

    private void goToHtml(String url) {
        Uri uri = Uri.parse(url);   //指定網址
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_VIEW);           //指定Action
        intent.setData(uri);                            //設置Uri
        startActivity(intent);        //啟動Activity
    }

    public void clearCache() {
        Observable.just(deleteFile(new File(MyApplication.getAppCacheDir() + "/KTReaderCache")))
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(AndroidSchedulers.mainThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Boolean>() {
                    @Override
                    public void accept(@NonNull Boolean aBoolean) throws Exception {
                        mOptionCache.setSummary(FileSizeUtil.getAutoFileOrFilesSize(MyApplication.getAppCacheDir() + "/KTReaderCache"));
                        Snackbar.make(getView(), "緩存已清除", Snackbar.LENGTH_SHORT).show();
                    }
                });
    }

    public boolean deleteFile(File file) {
        if (file.isFile()) {
            return file.delete();
        }

        if (file.isDirectory()) {
            File[] childFiles = file.listFiles();
            if (childFiles == null || childFiles.length == 0) {
                return file.delete();
            }

            for (File childFile : childFiles) {
                deleteFile(childFile);
            }
            return file.delete();
        }
        return false;
    }
}

這樣,我們的設置頁就大功告成了。

2.關于界面

之后的關于頁就沒啥東西了,只是簡單的鋪個界面,加幾個控件,其中相應的操作有:

  • 打開源碼地址,上面有講;
  • 把我的聯系方式復制到粘貼版;
  • 分享應用給好友。

復制功能

ClipboardManager manager = (ClipboardManager) this.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText("msg", "965846580");
manager.setPrimaryClip(clipData);
Snackbar.make(view,"我的qq號已經復制到剪切板啦( ?? .? ?? )?",Snackbar.LENGTH_SHORT).show();

分享功能

Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, "分享app");
sharingIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.share_txt));//string為感謝使用“KTReader”,您可以在https://github.com/yiuhet/KTReader查看源碼或下載;
startActivity(Intent.createChooser(sharingIntent, getString(R.string.share_app)));//string為分享-KTReader;

完整代碼
ui.activity.AboutActivity


public class AboutActivity extends BaseActivity {

    @BindView(R.id.toolbar)
    Toolbar mToolbar;
    @BindView(R.id.button_github)
    Button mButtonGithub;
    @BindView(R.id.button_jianshu)
    Button mButtonJianshu;
    @BindView(R.id.button_share)
    Button mButtonShare;
    @BindView(R.id.button_tucao)
    Button mButtonTucao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ButterKnife.bind(this);
        mToolbar.setTitle("關于");
        setSupportActionBar(mToolbar);
    }

    @Override
    protected int getLayoutRes() {
        return R.layout.activity_about;
    }

    @OnClick({R.id.button_github, R.id.button_jianshu, R.id.button_share, R.id.button_tucao})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.button_github:
                goToHtml("https://github.com/yiuhet/KTReader");
                break;
            case R.id.button_jianshu:
                goToHtml("http://www.lxweimin.com/u/8857dea54ec2");
                break;
            case R.id.button_share:
                Intent sharingIntent = new Intent(Intent.ACTION_SEND);
                sharingIntent.setType("text/plain");
                sharingIntent.putExtra(Intent.EXTRA_SUBJECT, "分享app");
                sharingIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.share_txt));
                startActivity(Intent.createChooser(sharingIntent, getString(R.string.share_app)));
                break;
            case R.id.button_tucao:
                ClipboardManager manager = (ClipboardManager) this.getSystemService(Context.CLIPBOARD_SERVICE);
                ClipData clipData = ClipData.newPlainText("msg", "965846580");
                manager.setPrimaryClip(clipData);
                Snackbar.make(view,"我的qq號已經復制到粘貼板啦( ?? .? ?? )?",Snackbar.LENGTH_SHORT).show();
                break;
        }
    }

    private void goToHtml(String url) {
        Uri uri = Uri.parse(url);   //指定網址
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_VIEW);           //指定Action
        intent.setData(uri);                            //設置Uri
        startActivity(intent);        //啟動Activity
    }

}

3.結語

KTReader(Kill Time Reader)這個小項目,開發周期近30天,算是初學Android的我做的第一個有著完整結構的應用,它的功能并不完善,也仍有一些bug存在,可為了完善它,我仍費了好大一番功夫,有過完成一個小功能而沾沾自喜,也有過被bug折磨的焦頭爛額。
總之在今天我完成了它,在開發期間我也學到了很多東西,感謝開源精神讓我成長并瞻仰大佬們的代碼,感謝各位大牛的干貨分享,讓我能降低學習成本快速學習。
我把這款應用的開發過程寫下來并分享,一是為了記錄以便今后使用,二是希望能夠結識到更多的朋友,當然如果能夠幫到你的話,就更加美好了,為了方便大家閱讀,我之后會寫一個總結篇。
KTReader這款應用,萬一有幸被屏幕前的你所發現,這是我的幸運,如果你能點個心,更是對我莫大的鼓勵。如果你是大神,請幫我指正我的不足之處;如果你和我一樣是初學者,歡迎和我一塊學習討論。這里留下我的聯系qq:965846580,加好友請備注Android。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容