上路傳送眼:
Android練手小項(xiàng)目(KTReader)基于mvp架構(gòu)(六)
Github地址: https://github.com/yiuhet/KTReader
上篇文章我們完成了項(xiàng)目的收藏和歷史功能元媚,
這篇我們將要完成該項(xiàng)目的最后的功能設(shè)置頁和關(guān)于頁。
先看看完成圖:
我們想要在設(shè)置和關(guān)于界面完成的功能有:
- 設(shè)置夜間模式
- 主題色的選擇
- 清空應(yīng)用緩存
- 查看項(xiàng)目源碼
- 分享好友應(yīng)用
所用到的知識點(diǎn)有:
- PreferenceFragment
- SharedPreferences
- File的刪除
- 代碼中設(shè)置theme
- 剪切板
可完善和加強(qiáng)的內(nèi)容或功能有:
- 檢查更新
- 關(guān)于UI界面美化
ps:由于這里的功能并不復(fù)雜的止,所以我就沒有按照mvp的架構(gòu)來寫具垫,直接把業(yè)務(wù)邏輯寫在了view層猬仁。(偷懶一波,畢竟期末事情太多浅妆,復(fù)習(xí)考試鸥诽,課程設(shè)計(jì),還有找實(shí)習(xí)工作/(ㄒoㄒ)/~~)
1. 設(shè)置界面
如果我們自己寫設(shè)置的界面疹娶,還要保存相關(guān)的設(shè)置,雖然也不是很復(fù)雜伦连,但是官方提供了更好的類:PreferenceFragment雨饺。
我們寫個(gè)fragment繼承自PreferenceFragment然后在xml文件中寫布局就好了,系統(tǒng)會(huì)自動(dòng)幫我們保存里面的偏好設(shè)置(通過SharedPreferences)惑淳。
下面簡述一下Preference的知識點(diǎn)(之后我會(huì)寫一篇詳解额港。):
XML
PreferenceScreen
根節(jié)點(diǎn),若一個(gè)xml文件中有嵌套的PreferenceScreen歧焦,點(diǎn)擊后將通過另一屏來顯示它包含的內(nèi)容移斩。PreferenceCategory
用來分組的東西,可以讓布局更有層次感绢馍。
組件的通用屬性有:android:key 唯一標(biāo)識id
android:defaultValue 默認(rèn)值
android:title 主標(biāo)題
android:summary 副標(biāo)題
用到的基本組件ListPreference
基本組件之一向瓷,點(diǎn)擊彈出列表對話框。android:dialogTitle
對話框標(biāo)題android:entries
列表顯示的文本()android:entryValues
實(shí)際系統(tǒng)保存的值舰涌,和entries一一對應(yīng)SwitchPreference
基本組件之一猖任,有on,off兩種狀態(tài)瓷耙。Preference
PreferenceFragment
我們可以通過相關(guān)的監(jiān)聽接口來寫對其需要的功能:
- onPreferenceClick(Preference preference)
點(diǎn)擊事件發(fā)生朱躺,回調(diào)該方法 - onPreferenceChange(Preference preference, Object newValue)
值改變刁赖,回調(diào)該方法
我們可以通過以下方式在其他activity中得到Preference保存的值:
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mPrefs.getString(key, defaultValue);
OK,知道了這些,要寫一個(gè)設(shè)置界面就很簡單了长搀。
1.要先在res文件下建個(gè)xml文件夾(如果沒有)宇弛,寫xml文件。
2.建立SettingsFragment繼承自PreferenceFragment源请,
3.在回調(diào)方法里寫具體操作枪芒。
4.創(chuàng)建單例,里面實(shí)現(xiàn)獲取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="設(shè)置">
<PreferenceCategory
android:title="基本設(shè)置">
<SwitchPreference
android:title="夜間模式"
android:summary="夜間保護(hù)你的眼睛"
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="其他設(shè)置">
<Preference
android:title="檢查更新"
android:summary="當(dāng)前版本: 1.0"
android:key="settings_check"/>
<Preference
android:title="查看源碼"
android:summary="給開發(fā)者github上點(diǎn)個(gè)Star"
android:key="settings_look"/>
</PreferenceCategory>
</PreferenceScreen>
數(shù)組文件為:
<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>
出來的效果是這樣的:
嗯 還不錯(cuò)病苗。
SettingsFragment
下面開始寫具體業(yè)務(wù)邏輯:
官方推薦的是使用fragment繼承自PreferenceFragment而不是繼承PreferenceActivity的activity來呈現(xiàn)界面和處理業(yè)務(wù)。所以我們跟著官方走使用PreferenceFragment症汹。我們要在onCreate中添加這樣一句話來加載xml文件:
addPreferencesFromResource(R.xml.preferences);
然后我們要初始化一些數(shù)據(jù)硫朦,寫個(gè)初始化方法:
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("當(dāng)前主題: %s",
mOptionTheme.getEntry() == null ? "愣頭青" : mOptionTheme.getEntry()));
mOptionCache.setSummary(FileSizeUtil.getAutoFileOrFilesSize(MyApplication.getAppCacheDir() + "/KTReaderCache"));
}
在這個(gè)方法里,我們創(chuàng)建了對應(yīng)xml文件中的控件對象背镇,通過設(shè)置的key咬展。
然后給它們設(shè)置了監(jiān)聽器。并對副標(biāo)題動(dòng)態(tài)顯示瞒斩。
下面就是在回調(diào)里寫對它們的監(jiān)聽處理:
首先是當(dāng)主題設(shè)置被改變時(shí)破婆,我們要更改副標(biāo)題,并且彈出提示框胸囱,提示用戶重啟以使設(shè)置生效祷舀。
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mOptionTheme) {
// mOptionTheme.setSummary("當(dāng)前主題: " + mOptionTheme.getEntry());
if (newValue.equals("indigo")) {
mOptionTheme.setSummary("當(dāng)前主題: 愣頭青");
} else if (newValue.equals("pink")) {
mOptionTheme.setSummary("當(dāng)前主題: 少女粉");
}else if (newValue.equals("red")) {
mOptionTheme.setSummary("當(dāng)前主題: 畫韓紅");
}else if (newValue.equals("green")) {
mOptionTheme.setSummary("當(dāng)前主題: 原諒綠");
}else if (newValue.equals("purple")) {
mOptionTheme.setSummary("當(dāng)前主題: 基佬紫");
}
}
Snackbar.make(getView(),"主題切換成" +
"功,重啟應(yīng)用后生效",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;
}
之后我們對清除緩存的監(jiān)聽處理為彈出對話框再次詢問是否清除,防止誤操作烹笔,然后當(dāng)用戶選擇清除時(shí)裳扯,刪除緩存文件。對查看源碼處理為跳轉(zhuǎn)到項(xiàng)目github網(wǎng)址:
@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("當(dāng)前主題: %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("當(dāng)前主題: " + mOptionTheme.getEntry());
if (newValue.equals("indigo")) {
mOptionTheme.setSummary("當(dāng)前主題: 愣頭青");
} else if (newValue.equals("pink")) {
mOptionTheme.setSummary("當(dāng)前主題: 少女粉");
}else if (newValue.equals("red")) {
mOptionTheme.setSummary("當(dāng)前主題: 畫韓紅");
}else if (newValue.equals("green")) {
mOptionTheme.setSummary("當(dāng)前主題: 原諒綠");
}else if (newValue.equals("purple")) {
mOptionTheme.setSummary("當(dāng)前主題: 基佬紫");
}
}
Snackbar.make(getView(),"主題切換成" +
"功,重啟應(yīng)用后生效",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); //指定網(wǎng)址
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW); //指定Action
intent.setData(uri); //設(shè)置Uri
startActivity(intent); //啟動(dòng)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;
}
}
這樣,我們的設(shè)置頁就大功告成了。
2.關(guān)于界面
之后的關(guān)于頁就沒啥東西了嚣鄙,只是簡單的鋪個(gè)界面,加幾個(gè)控件冤吨,其中相應(yīng)的操作有:
- 打開源碼地址,上面有講饶套;
- 把我的聯(lián)系方式復(fù)制到粘貼版漩蟆;
- 分享應(yīng)用給好友。
復(fù)制功能:
ClipboardManager manager = (ClipboardManager) this.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText("msg", "965846580");
manager.setPrimaryClip(clipData);
Snackbar.make(view,"我的qq號已經(jīng)復(fù)制到剪切板啦( ?? .? ?? )?",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("關(guān)于");
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.reibang.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號已經(jīng)復(fù)制到粘貼板啦( ?? .? ?? )?",Snackbar.LENGTH_SHORT).show();
break;
}
}
private void goToHtml(String url) {
Uri uri = Uri.parse(url); //指定網(wǎng)址
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW); //指定Action
intent.setData(uri); //設(shè)置Uri
startActivity(intent); //啟動(dòng)Activity
}
}
3.結(jié)語
KTReader(Kill Time Reader)這個(gè)小項(xiàng)目,開發(fā)周期近30天,算是初學(xué)Android的我做的第一個(gè)有著完整結(jié)構(gòu)的應(yīng)用扔仓,它的功能并不完善褐奥,也仍有一些bug存在,可為了完善它翘簇,我仍費(fèi)了好大一番功夫撬码,有過完成一個(gè)小功能而沾沾自喜,也有過被bug折磨的焦頭爛額版保。
總之在今天我完成了它呜笑,在開發(fā)期間我也學(xué)到了很多東西,感謝開源精神讓我成長并瞻仰大佬們的代碼彻犁,感謝各位大牛的干貨分享叫胁,讓我能降低學(xué)習(xí)成本快速學(xué)習(xí)。
我把這款應(yīng)用的開發(fā)過程寫下來并分享汞幢,一是為了記錄以便今后使用驼鹅,二是希望能夠結(jié)識到更多的朋友,當(dāng)然如果能夠幫到你的話森篷,就更加美好了输钩,為了方便大家閱讀,我之后會(huì)寫一個(gè)總結(jié)篇仲智。
KTReader這款應(yīng)用买乃,萬一有幸被屏幕前的你所發(fā)現(xiàn),這是我的幸運(yùn)钓辆,如果你能點(diǎn)個(gè)心剪验,更是對我莫大的鼓勵(lì)。如果你是大神前联,請幫我指正我的不足之處功戚;如果你和我一樣是初學(xué)者,歡迎和我一塊學(xué)習(xí)討論蛀恩。這里留下我的聯(lián)系qq:965846580,加好友請備注Android茂浮。