美文网首页Android技术栈Android开发Android技术知识
建站四部曲之移动端篇(Android+上线)

建站四部曲之移动端篇(Android+上线)

作者: e4e52c116681 | 来源:发表于2018-12-15 14:29 被阅读44次
    本系列分为四篇:

    零、前言

    本系列为了总结一下手上的知识,致敬我的2018
    本篇的重点在于:后端数据在移动端的展现
    本篇总结的技术点:
    材料设计串烧Retrofit+RxJava访问请求Retrofit提交表单Retrofit缓存的实现(简)
    搜索功能的实现MVP模式的思考单元测试(简)
    App的混淆打包将App上传到服务器,提供下载地址


    一、材料设计的综合使用:

    1.布局概览

    最外层是一个DrawerLayout并和Toolbar相关联
    DrawerLayout主要分为左和中间两块,核心的是中间,左边顺带用一下NavigationView
    中间主页面由AppBarLayout+CollapsingToolbarLayout+Toolbar祖孙三人打头阵
    中间主题由RecyclerView骁勇杀敌,最底下由BottomNavigationBar收尾
    另外FloatingActionButton+bottom_sheet补刀,bottom_sheet中藏着搜索功能

    布局概览.png
    2.效果图一览

    总体来说和网页端风格保持一致

    Android原生版 网页版手机端

    3.布局与材料设计的控件使用

    布局就不贴了,挺多的,也没什么技术含量,有兴趣的看源码吧

    有关材料设计,我写过一个系列:详见--Android材料设计Material Design 开篇前言

    3.1:BottomNavigationBar的使用:

    为了方便起见,我写了一个IconItem类,并定义了一个常量数组:

    ------------------
    public class IconItem {
        private int color;
        private int iconId;
        private String info;
        //其他省略...
    }
    ------------------
    public static final IconItem[] BNB_ITEM = new IconItem[]{
            new IconItem("Android", R.drawable.icon_android, R.color.color4Android),
            new IconItem("Spring", R.drawable.icon_spring_boot, R.color.color4SpringBoot),
            new IconItem("React", R.drawable.icon_react, R.color.color4React),
            new IconItem("编程随笔", R.drawable.icon_note, R.color.color4Note),
            new IconItem("系列文章", R.drawable.icon_code, R.color.color4Ser),
    };
    ------------------使用:---
    IconItem[] items = Cons.BNB_ITEM;
    for (IconItem item : items) {
        mIdBnb.addItem(new BottomNavigationItem(item.getIconId(), item.getInfo())
                .setActiveColorResource(item.getColor()));
    }
    mIdBnb.initialise();
    

    3.2:SwipeRefreshLayout的使用:
    //每转一圈,换一种颜色
    mIdSrl.setColorSchemeColors(
            0xffF60C0C,//红
            0xffF3B913,//橙
            0xffE7F716,//黄
            0xff3DF30B,//绿
            0xff0DF6EF,//青
            0xff0829FB,//蓝
            0xffB709F4//紫
    );
    mIdSrl.setOnRefreshListener(() -> {
        //TODO刷新逻辑
    });
    

    3.3:DrawerLayout与Toolbar的结合
    ------------------------------
    mABDT = new ActionBarDrawerToggle(
                    this, mIdDlRoot, mToolbar, R.string.str_open, R.string.str_close);
    mIdDlRoot.addDrawerListener(mABDT);
    
    ------------------------------
    
    @Override
    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mABDT.syncState();//加了这个才有酷炫的按钮变化
    }
    

    3.4:BottomSheet与FloatingActionButton的结合
    mBottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet);
    mIdFab.setOnClickListener(v -> {
        if (isOpen) {
            mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        } else {
            mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        }
        isOpen = !isOpen;
    });
    

    4.伴随移动的Behavior
    祖孙三头.gif
    移出 移入
    FloatingActionButton伴随动画:FabFollowListBehavior
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/11/30 0030:14:34<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:FloatingActionButton伴随动画
     */
    public class FabFollowListBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
        private static final int MIN_DY = 30;
    
        public FabFollowListBehavior(Context context, AttributeSet attributeSet) {
            super(context, attributeSet);
        }
    
        /**
         * 初始时不调用,滑动时调用---一次滑动过程,之调用一次
         */
        @Override
        public boolean onStartNestedScroll(
                @NonNull CoordinatorLayout coordinatorLayout,
                @NonNull FloatingActionButton child,
                @NonNull View directTargetChild,
                @NonNull View target, int axes, int type) {
            return true;
        }
    
        /**
         * @param dyConsumed 每次回调前后的Y差值
         */
        @Override
        public void onNestedScroll(
                @NonNull CoordinatorLayout coordinatorLayout,
                @NonNull FloatingActionButton child,
                @NonNull View target, int dxConsumed,
                int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
            super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
    
            //平移隐现
            if (dyConsumed > MIN_DY) {//上滑:消失
                showOrNot(coordinatorLayout, child, false).start();
            } else if (dyConsumed < -MIN_DY) {//下滑滑:显示
                showOrNot(coordinatorLayout, child, true).start();
            }
    
            //仅滑动时消失
    //        if (dyConsumed > MIN_DY || dyConsumed < -MIN_DY) {//上滑:消失
    //            showOrNot(child).start();
    //        }
        }
    
        private Animator showOrNot(CoordinatorLayout coordinatorLayout, final View fab, boolean show) {
            //获取fab头顶的高度
            int hatHeight = coordinatorLayout.getBottom() - fab.getBottom() + fab.getHeight();
            int end = show ? 0 : hatHeight;
            float start = fab.getTranslationY();
            ValueAnimator animator = ValueAnimator.ofFloat(start, end);
            animator.addUpdateListener(animation ->
                    fab.setTranslationY((Float) animation.getAnimatedValue()));
            return animator;
        }
    
        private Animator showOrNot(final View fab) {
            //获取fab头顶的高度
            ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
    
            animator.addUpdateListener(animation -> {
                fab.setScaleX((Float) animation.getAnimatedValue());
                fab.setScaleY((Float) animation.getAnimatedValue());
            });
            return animator;
        }
    }
    
    BottomNavigationBar伴随列表显隐的Behavior:BnbFollowListBehavior
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/11/30 0030:9:35<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:BottomNavigationBar伴随列表显隐的Behavior
     */
    public class BnbFollowListBehavior extends BottomVerticalScrollBehavior<BottomNavigationBar> {
    
        public BnbFollowListBehavior(Context context, AttributeSet attributeSet) {
            super();
        }
    }
    

    推荐想安卓看齐,写在string.xml里,方便修改
    <string name="followListBehavior">com.toly1994.mycode.app.behavior.BnbFollowListBehavior</string>
    <string name="behavior_fab_follow">com.toly1994.mycode.app.behavior.FabFollowListBehavior</string>
    

    FloatingActionButton伴随动画定义在FloatingActionButton伴随动画按钮的标签内
    BottomNavigationBar伴随列表显隐的Behavior 写在RecyclerView标签内
    Behavior的详细介绍可见:Android材料设计之Behavior攻坚战


    二、MVP的思路

    1.概述:
    蓝色白斜字是接口
    橙色虚线是类方法的引线
    蓝色虚线是流程线
    天蓝色的是普通类
    
    左中右分别是MPV,模型层(M)负责数据的获取,通过Callback回调在控制层(P)使用
    控制层(P)注意进行模型层(M)和视图层(V)的粘合,通过逻辑进行不同的视图展现
    也就是说我在写P的实现类中,管你MV怎么实现的么,你家老子(M,V的接口)在我手上,我还怕什么
    在写视图层(V)时,V手里也有控制层的老子(P的接口),所以V也是怎么想的
    
    所以无论写视图层,数据层,控制层,只要把接口定义好,便可以分工去写,互不影响  
    这也就是面相接口编程的有点,有些人视图非常棒,可以专门做视图层,
    网络、数据库强的可以专门做模型层等等...
    
    就像找1个全才和找3个精通某一门的人去做同一件事一样,理论上来说,后者做的会更周到,更轻松。
    分工明确有助于思路的清晰和方法的复用
    
    MVP思路.png
    2.接口先搞起来

    把ILoadingView直接放到INoteView也可以,看个人喜好吧

    2.1.视图层核心
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/12/14 0014:7:49<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:加载和加载完毕的视图
     */
    public interface ILoadingView {
        /**
         * 正在加载
         */
        void loading();
    
        /**
         * 加载完毕
         */
        void loaded();
    }
    ----------------------------------------
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/12/14 0014:7:48<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:视图层核心
     */
    public interface INoteView<T> extends ILoadingView {
    
        /**
         * 页面渲染数据
         * @param dataList
         */
        void reader(List<T> dataList);
    
        /**
         * 页面处理错误
         * @param e
         */
        void error(ErrorEnum e);
    }
    

    2.2.控制层:
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/12/14 0014:20:27<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:控制层
     */
    public interface IPresenter<T> {
        /**
         * 根据所属区域更新视图
         *
         * @param area 范围
         * @param offset 查询偏移值
         * @param count 查询条数
         */
        void updateByArea(String area, int offset, int count);
    
        /**
         * 根据查询名称更新视图
         *
         * @param name 范围
         * @param offset 查询偏移值
         * @param count 查询条数
         */
        void updateByName(String name, int offset, int count);
    }
    

    2.3.模型层
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/12/14 0014:13:43<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:数据模型层
     */
    public interface INoteModel<T> {
        /**
         * 查询所有
         * @param callback 回调
         * @param offset 查询偏移值
         * @param page 查询条数
         */
        void getData(Callback<T> callback, int offset, int page);
    
        /**
         * 根据所属区域查询数据
         * @param callback 回调
         * @param area 范围
         * @param offset 查询偏移值
         * @param page 查询条数
         */
        void getDataByArea(Callback<T> callback, String area, int offset, int page);
    
        /**
         * 根据名称查询数据(搜索)
         * @param callback 回调
         * @param name 范围
         * @param offset 查询偏移值
         * @param page 查询条数
         */
        void getDataByName(Callback<T> callback, String name, int offset, int page);
        
        /**
         * 插入模型
         * @param params
         */
        void insertModel(Map<String, String> params);
    }
    
    ----------------------------模型层数据回调接口-----
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/12/14 0014:13:43<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:模型层数据回调接口
     */
    public interface Callback<T> {
        /**
         * 开始加载
         */
        void onStartLoad();
    
        /**
         * 成功
         * @param dataList 数据
         */
        void onSuccess(List<T> dataList);
    
        /**
         * 错误
         * @param e 错误
         */
        void onError(ErrorEnum e);
    }
    

    2.4.错误类型枚举

    可以自定义错误类型,以便之后根据不同错误显示不同界面

    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/12/14 0014:7:58<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:错误类型
     */
    public enum ErrorEnum {
        EXCEPTION(500, "服务器"),
        NOT_FOUND(102, "未知id"),
        IO(1, "IO异常"),
        NO_NET(2, "无网络"),
        NET_LINK(3, "网络连接异常");
    
        private int code;
        private String msg;
    
        ErrorEnum(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public int getCode() {
            return code;
        }
    
        public String getMsg() {
            return msg;
        }
    }
    

    3.模型层的实现

    数据是核心,先把数据拿在手上,心理才踏实,使用Retrofit+RxJava
    下图是最简单的Retrofit+RxJava获取数据的方式

    //rxjava2
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
    //retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'//核心库
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'//json转换器
    implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'//配合Rxjava 使用
    
    RX+Ret.png

    3.1:接口先行:NoteApi.java

    在此之前回顾一下服务器的接口

    ----查询所有:http://192.168.43.60:8089/api/android/note
    ----查询偏移12条,查询12条(即12条为一页的第2页):
    http://192.168.43.60:8089/api/android/note/12/12
    ----按区域查询(A为Android数据,SB为SpringBoot数据,Re为React数据)
    http://192.168.43.60:8089/api/android/note/area/A
    http://192.168.43.60:8089/api/android/note/area/A/12/12
    ----按部分名称查询
    http://192.168.43.60:8089/api/android/note/name/材料
    http://192.168.43.60:8089/api/android/note/name/材料/2/2
    ----按类型名称查询(类型定义表见第一篇)
    http://192.168.43.60:8089/api/android/note/name/ABCS
    http://192.168.43.60:8089/api/android/note/name/ABCS/2/2
    ----按id名称查:http://192.168.43.60:8089/api/android/note/12
    添-POST请求:http://192.168.43.60:8089/api/android/note
    
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/12/13 0013:19:48<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:API接口
     */
    public interface NoteApi {
        /**
         * 查询所有操作
         */
        @GET("api/android/note/{offset}/{page}")
        Observable<ResultBean> findAll(@Path("offset") int offset, @Path("page") int page);
        /**
         * 根据范围查询
         */
        @GET("api/android/note/area/{op}/{offset}/{page}")
        Observable<ResultBean> findByArea(@Path("op") String op, @Path("offset") int offset, @Path("page") int page);
        /**
         * 根据类型查询
         */
        @GET("api/android/note/type/{type}/{offset}/{page}")
        Observable<ResultBean> findByType(@Path("type") String op, @Path("offset") int offset, @Path("page") int page);
        /**
         * 根据名字查询
         */
        @GET("api/android/note/name/{type}/{offset}/{page}")
        Observable<ResultBean> findByName(@Path("type") String type, @Path("offset") int offset, @Path("page") int page);
        /**
         * 插入操作
         */    
        @FormUrlEncoded
        @POST("api/android/note")
        Observable<ResultBean> insert(@FieldMap Map<String, String> params);
    }
    

    3.2:ResultBean和NoteBean实体类
    这个和后端的实体类保持一直,你可以直接用AS的插件直接生成  
    也可以把后端的实体类拿来用,挺长的,不贴了,没有技术含量,详见源码
    

    3.3:获取数据核心逻辑
    
    public class NoteModel implements INoteModel<ResultBean.NoteBean> {
    
        private static final String TAG = "NoteModel";
        private NoteApi mNoteApi;
    
        public NoteModel() {
        mNoteApi = new Retrofit.Builder()
               .addConverterFactory(GsonConverterFactory.create())//json转换成JavaBean
               .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
               .baseUrl(BASE_URL)
               .build().create(NoteApi.class);
        }
        
         @Override
     public void getData(Callback<ResultBean.NoteBean> callback, int offset, int page) {
         callback.onStartLoad();
         doSubscribe(callback, mNoteApi.findAll(offset, page));
     }
     
         @Override
        public void getDataByArea(Callback<ResultBean.NoteBean> callback, String area, int offset, int page) {
            callback.onStartLoad();
            doSubscribe(callback, mNoteApi.findByArea(area, offset, page));
    
        }
    
        @Override
        public void getDataByName(Callback<ResultBean.NoteBean> callback, String name, int offset, int page) {
            callback.onStartLoad();
            doSubscribe(callback, mNoteApi.findByName(name, offset, page));
    
        }
     
     /**
      * 执行api返回的Observable
      *
      * @param callback 回调函数
      * @param apiAll   Observable
      */
     private void doSubscribe(Callback<ResultBean.NoteBean> callback, Observable<ResultBean> apiAll) {
         apiAll.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                 .subscribe(new Observer<ResultBean>() {
                     @Override
                     public void onSubscribe(Disposable d) {
                     }
                     @Override
                     public void onNext(ResultBean resultBean) {
                         callback.onSuccess(resultBean.getData());
                     }
                     @Override
                     public void onError(Throwable e) {
                         callback.onError(ErrorEnum.NET_LINK);
                     }
                     @Override
                     public void onComplete() {
                     }
                 });
     }
    }
    

    3.4:测试接口(单元测试)

    这里做一些单元测试,因为还没有实现P和V,看模型层是否正确,最后的方法就是单元测试
    安卓里的单元测试很简单,这里获取数据比对一下条数,通过则说明数据是对的

    @RunWith(AndroidJUnit4.class)
    public class ExampleInstrumentedTest {
        @Test
        public void getAllData() {
            NoteModel model = new NoteModel();
            model.getData(new Callback<ResultBean.NoteBean>() {
                @Override
                public void onStartLoad() {
                }
    
                @Override
                public void onSuccess(List<ResultBean.NoteBean> dataList) {
                    assertEquals(12, dataList.size());
                }
    
                @Override
                public void onError(ErrorEnum e) {
    
                }
            }, 0, 12);
        }
    
        @Test
        public void getDataByName() {
            NoteModel model = new NoteModel();
            model.getDataByName(new Callback<ResultBean.NoteBean>() {
                @Override
                public void onStartLoad() {
                }
    
                @Override
                public void onSuccess(List<ResultBean.NoteBean> dataList) {
                    assertEquals(12, dataList.size());
                }
    
                @Override
                public void onError(ErrorEnum e) {
    
                }
            }, "A", 0, 12);
        }
    }
    
    单元测试.png

    ok,测试通过,去视图层吧


    4.视图层的实现:HomePagerView.java

    findViewByid就不写了...,loading使用SwipeRefreshLayout

    4.1:方法的实现
    private RecyclerView mHomeRv;//RecyclerView
    private SwipeRefreshLayout mIdSrl;//下拉刷新
    private IPresenter<ResultBean.NoteBean> mPagerPresenter;//控制层
    
    @Override
    public void reader(List<ResultBean.NoteBean> dataList) {
        HomeAdapter ListAdapter = new HomeAdapter(dataList);
        mHomeRv.setAdapter(ListAdapter);
        LinearLayoutManager llm = new LinearLayoutManager(this);
        GridLayoutManager gm = new GridLayoutManager(this, 2);
        mHomeRv.setLayoutManager(gm);
    }
    
    @Override
        public void loading() {
            mIdSrl.setRefreshing(true);
        }
    
        @Override
        public void loaded() {
            mIdSrl.setRefreshing(false);
    
        }
    

    4.2:RecyclerView的适配器

    为了方便,这里用Picasso加载网络图片,自带缓存功能

    public class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyViewHolder> {
        private Context mContext;
        private List<ResultBean.NoteBean> mData;
    
        public HomeAdapter(List<ResultBean.NoteBean> data) {
            mData = data;
        }
    
        @NonNull
        @Override
        public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            mContext = parent.getContext();
            View view = LayoutInflater.from(mContext).inflate(R.layout.item_a_card, parent, false);
            return new MyViewHolder(view);
        }
    
        @Override
        public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
    
            ResultBean.NoteBean note = mData.get(position);
    
    
            if (note.getName().equals(mData.get(0).getName())) {
                holder.mIdNewTag.setVisibility(View.VISIBLE);
            } else {
                holder.mIdNewTag.setVisibility(View.GONE);
    
            }
    
            Picasso.get()
                    .load(note.getImgUrl())
                    .into(holder.mIvCover);
    
            holder.mIvTvTitle.setText(note.getName());
            holder.mIdTvType.setText(note.getType());
        }
    
        @Override
        public int getItemCount() {
            return mData.size();
        }
    
        class MyViewHolder extends RecyclerView.ViewHolder {
            public View mIdNewTag;
            public TextView mIvTvTitle;
            public ImageView mIvCover;
            public TextView mIdTvType;
    
            public MyViewHolder(View itemView) {
                super(itemView);
                mIvTvTitle = itemView.findViewById(R.id.iv_tv_title);
                mIvCover = itemView.findViewById(R.id.iv_cover);
                mIdTvType = itemView.findViewById(R.id.id_tv_type);
                mIdNewTag = itemView.findViewById(R.id.id_new_tag);
            }
        }
    }
    

    5.控制层

    前两层实现之后,这层就简单了

    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/12/14 0014:13:57<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:控制层
     */
    public class PagerPresenter extends BasePresenter implements IPresenter<ResultBean.NoteBean> {
        private INoteView<ResultBean.NoteBean> mNoteView;
        private INoteModel<ResultBean.NoteBean> mModel;
        private Callback<ResultBean.NoteBean> mCallback;
    
        public PagerPresenter(INoteView<ResultBean.NoteBean> noteView) {
            mNoteView = noteView;
            mModel = new NoteModel();
            initCallBack();
        }
    
        private void initCallBack() {//初始化回调函数
            mCallback = new Callback<ResultBean.NoteBean>() {
                @Override
                public void onStartLoad() {
                    mNoteView.loading();
                }
    
                @Override
                public void onSuccess(List<ResultBean.NoteBean> dataList) {
                    mNoteView.reader(dataList);
                    mNoteView.loaded();
                }
    
                @Override
                public void onError(ErrorEnum e) {
                    mNoteView.error(e);
                    mNoteView.loaded();
                }
            };
        }
    
        @Override
        public void updateByArea(String area, int offset, int count) {
            mModel.getDataByArea(mCallback, area, offset, count);
        }
    
        @Override
        public void updateByName(String name, int offset, int count) {
            mModel.getDataByName(mCallback, name, offset, count);
        }
    }
    

    6.运作:HomePagerView里,两句话
    mPagerPresenter = new PagerPresenter(this);
    mPagerPresenter.updateByArea("A", 0, 12);
    

    三、相关操作

    1.下拉刷新和点击切换:
    1.1:效果一览
    下拉刷新 点击切换

    1.2:下拉刷新

    就这么简单

    mIdSrl.setOnRefreshListener(() -> {
        mPagerPresenter.updateByArea(area, 0, 1000);
    });
    

    1.3:点击切换

    也就是根据点击出判断类型,根据类型使用控制层刷新视图

    private String area = "A";
    ------------------------------------------
    mIdBnb.setTabSelectedListener(new BottomNavigationBar.OnTabSelectedListener() {
       @Override
       public void onTabSelected(int position) {
           switch (position) {
               case 0:
                   area = "A";
                   mIdCtlBar.setTitle("Android技术栈");
                   mIdIvHead.setImageResource(R.mipmap.bg_android);
                   break;
               case 1:
                   area = "SB";
                   mIdCtlBar.setTitle("SpringBoot技术栈");
                   mIdIvHead.setImageResource(R.mipmap.bg_springboot);
                   break;
               case 2:
                   area = "Re";
                   mIdCtlBar.setTitle("React技术栈");
                   mIdIvHead.setImageResource(R.mipmap.bg_react);
                   break;
               case 3:
                   area = "Note";
                   mIdCtlBar.setTitle("随笔编程杂谈录");
                   mIdIvHead.setImageResource(R.mipmap.menu_bg);
                   break;
               case 4:
                   area = "A";
                   mIdCtlBar.setTitle("系列文章");
                   break;
           }
           mPagerPresenter.updateByArea(area, 0, 1000);
       }
       @Override
       public void onTabUnselected(int position) {
       }
    
       @Override
       public void onTabReselected(int position) {
    
       }
    );
    

    2.添加和搜索功能
    添加功能 搜索功能
    2.1:搜索功能:

    也就是根据名称匹配输入字符,再去查询,
    点击是str是输入框字符串,执行mPagerPresenter的updateByName

    mPagerPresenter.updateByName(str, 0, 1000);
    mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
    isOpen = false;
    

    2.2:添加操作

    这个稍微有点麻烦,需要一个视图对话框

    //接口---NoteApi
    @FormUrlEncoded
    @POST("api/android/note")
    Observable<ResultBean> insert(@FieldMap Map<String, String> params);
    
    //模型层---NoteModel
    @Override
    public void insertModel(Map<String, String> params) {
        doSubscribe(null, mNoteApi.insert(params));
    }
    
    //控制层---PagerPresenter
    @Override
    public void addItem(Map<String, String> params) {
        mModel.insertModel(params);
    }
    
    //视图层:HomePagerView
    
     @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()) {
                case R.id.tab_add:
                    doAdd(this)
                    break;
            }
            return super.onOptionsItemSelected(item);
        }
    
        public static void doAdd(Context context) {
            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            View dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_add, null);
            EditText title = dialogView.findViewById(R.id.et_upload_title);
            EditText url = dialogView.findViewById(R.id.et_upload_path);
            DatePicker cost_date = dialogView.findViewById(R.id.cost_date);
    
            builder.setTitle("添加文章");
            builder.setView(dialogView);
            builder.setPositiveButton("确定", (dialog, which) -> {
                String createTime = cost_date.getYear() + "-" + (cost_date.getMonth() + 1) + "-" + cost_date.getDayOfMonth();
    
                ResultBean.NoteBean noteBean = new ResultBean.NoteBean();
                String name = title.getText().toString();
                String jianshuUrl = url.getText().toString();
                String imgUrl = "8a11d27d58f4c1fa4488cf39fdf68e76.png";
                noteBean.setImgUrl(imgUrl);
    
                Map<String, String> hashMap = new HashMap<>();
                hashMap.put("type","C");
                hashMap.put("name",name);
                hashMap.put("jianshuUrl",jianshuUrl);
                hashMap.put("juejinUrl","---");
                hashMap.put("imgUrl",imgUrl);
                hashMap.put("createTime",createTime);
                hashMap.put("info","hh");
                hashMap.put("area","A");
                hashMap.put("localPath","---");
                 mPagerPresenter.addItem(params);
            });
    
            builder.setNegativeButton("取消", null);
            builder.create().show();
        }
    

    四、混淆打包和上线

    1.混淆:
    -----app/build.gradle------开启混淆
    buildTypes {
            release {
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    
    ----app/proguard-rules.pro------混淆配置
    
    -ignorewarnings#忽略警告
    # Retrofit
    -dontnote retrofit2.Platform
    -dontnote retrofit2.Platform$IOS$MainThreadExecutor
    -dontwarn retrofit2.Platform$Java8
    -keepattributes Signature
    -keepattributes Exceptions
    
    # okhttp
    -dontwarn okio.**
    
    # Gson
    -keep class com.toly1994.mycode.bean.**{*;} # 自定义数据模型的bean目录
    

    2.签名打包

    混淆打包后,差不多比debug的包小一半,感觉还不错,亲测可用

    签名.png ttt.png
    3.上线

    好吧,不是上传到各大市场,毕竟现在个人app很难上去
    在前端界面上提供下载地址,很简单,拷到服务器上就行了,然后访问就能下载了

    下载.png

    4.前端React稍微修改:

    这样点击时就能下载了

    下载3.png 下载2.png

    基本上的点都讲到了,虽然不是面面俱到,整体hold住就差不多了
    源码在最后,有兴趣的可以看看,总结以下,到此为止,用了五天的时间做了以下事:

    1.使用SpringBoot结合Mybatis搭建了一个Restful接口的线上服务端
    2.使用Python的selenium库爬取简书主页的文章信息并用java将数据通过网络请求插入数据库  
    3.使用React搭建前端显示界面,scss的样式使用和axios的网络请求以及移动端的网页适配
    4.使用Java基于Android构建一个材料设计风格的移动端应用,以及上线
    5.写了这四篇长文,总的来说还是很有手绘的,最起码知识串起来了
    

    后记:捷文规范

    1.本文成长记录及勘误表
    项目源码 日期 备注
    V0.1-github 2018-12-15 建站四部曲之移动端篇(Android+上线)
    2.更多关于我
    笔名 QQ 微信 爱好
    张风捷特烈 1981462002 zdl1994328 语言
    我的github 我的简书 我的掘金 个人网站
    3.声明

    1----本文由张风捷特烈原创,转载请注明
    2----欢迎广大编程爱好者共同交流
    3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
    4----看到这里,我在此感谢你的喜欢与支持


    icon_wx_200.png

    相关文章

      网友评论

        本文标题:建站四部曲之移动端篇(Android+上线)

        本文链接:https://www.haomeiwen.com/subject/zxlahqtx.html