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.jianshu.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阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容