一个小时打造新闻app

作者: 五谷观精分道长 | 来源:发表于2016-10-25 09:36 被阅读21147次

    前言

    作为一个新手,学完基础总想做点什么东西出来。于是我试着去模仿那些优秀的开源作品。
    模仿作品:LookLook开源项目
    经过一些波折和学习,写下模仿过程。

    一个小时打造新闻app

    实际上我花了大概三天才弄懂所有的东西,不过有了经验确实可以在一个小时里完成。

    使用框架:

    rxjava和retrofit以及一个开源扩展的recyclerview和注解框架butterknife
    集体依赖如下:

    dependencies {
        compile fileTree(include: ['*.jar'], dir: 'libs')
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        compile 'com.android.support:appcompat-v7:24.2.1'
        compile 'com.android.support:design:24.2.1'
        testCompile 'junit:junit:4.12'
        //依赖注解
        //依赖添加
        compile 'com.jakewharton:butterknife:8.4.0'
        apt 'com.jakewharton:butterknife-compiler:8.4.0'
        compile 'com.google.code.gson:gson:2.7'
        //高级的recyclerview
        compile 'com.jude:easyrecyclerview:4.2.3'
        compile 'com.android.support:recyclerview-v7:24.2.0'
        //rxjava
        compile 'com.squareup.retrofit2:retrofit-converters:2.1.0'
        compile 'com.squareup.retrofit2:converter-gson:2.1.0'
        compile 'io.reactivex:rxandroid:1.2.1'
        compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
        compile 'com.squareup.retrofit2:retrofit:2.1.0'
        compile 'com.squareup.retrofit2:converter-scalars:2.1.0'
        compile 'com.github.bumptech.glide:glide:3.7.0'
    }
    
    

    开始制作app

    界面制作:

    新建项目,选择模板---->调整模板


    QQ截图20161025085115.png

    菜单调整

    可以看到有menu里面两个文件
    一个是主菜单,显示在Toobar上面
    另一个是抽屉的菜单,按需修改即可。
    activity_main_drawer.xml

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
    
        <group android:checkableBehavior="single">
    
            <item
                android:id="@+id/nav_camera"
                android:icon="@drawable/ic_menu_slideshow"
                android:title="新闻精选" />
            <item
                android:id="@+id/nav_gallery"
                android:icon="@drawable/ic_face_black_24dp"
                android:title="轻松一刻" />
            <item
                android:id="@+id/nav_slideshow"
                android:icon="@drawable/ic_menu_gallery"
                android:title="每日美图" />
            <item
                android:id="@+id/nav_manage"
                android:icon="@drawable/ic_menu_manage"
                android:title="应用推荐" />
        </group>
    
        <item android:title="其他">
            <menu>
                <item
                    android:id="@+id/nav_share"
                    android:icon="@drawable/ic_menu_share"
                    android:title="软件分享" />
                <item
                    android:id="@+id/nav_send"
                    android:icon="@drawable/ic_menu_send"
                    android:title="软件关于" />
            </menu>
        </item>
    
    </menu>
    

    抽屉除了menu还有上面一部分,可以设置头像和签名。
    nav_header_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="@dimen/nav_header_height"
        android:background="@drawable/side_nav_bar"
        android:gravity="bottom"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:theme="@style/ThemeOverlay.AppCompat.Dark"
        android:orientation="vertical">
    
        <ImageView
            android:layout_gravity="center"
            android:id="@+id/imageView"
            android:layout_width="100dp"
            android:layout_height="100dp"
            app:srcCompat="@drawable/ic_app_icon" />
    
        <TextView
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="一日之计在于晨,一年之计在于春。"
            android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
    
        <TextView
            android:gravity="center"
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="1458476478@qq.com" />
    
    </LinearLayout>
    

    主界面大概这就可以了,剩下的就是要动态添加fragement到FragLayout里面去。

    数据获取

    首先新建fragment_news,布局文件只需要一个EasyRecyclerView即可

    添加依赖

    //高级的recyclerview
        compile 'com.jude:easyrecyclerview:4.2.3'
        compile 'com.android.support:recyclerview-v7:24.2.0'
    
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <com.jude.easyrecyclerview.EasyRecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:recyclerClipToPadding="true"
            app:recyclerPadding="8dp"
            app:recyclerPaddingBottom="8dp"
            app:recyclerPaddingLeft="8dp"
            app:recyclerPaddingRight="8dp"
            app:recyclerPaddingTop="8dp"
            app:scrollbarStyle="insideOverlay"
            app:scrollbars="none" />
    </LinearLayout>
    

    使用rxjava和retrofit获取json数据

    我的数据来自天性数据,只需要注册即可获得APIKEY。
    数据请求核心代码:

    创建retrofit的请求接口

    public interface ApiService{
        @GET("social/")
        Observable <NewsGson> getNewsData(@Query("key")String key,@Query("num") String num,@Query("page") int page);
    

    注意返回的是Gson数据而且设置为"被观察者"

    数据获取函数:

    private void getData() {
            Log.d("page", page + "");
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://api.tianapi.com/")
                    //String
                    .addConverterFactory(ScalarsConverterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create())//添加 json 转换器
                    //    compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加 RxJava 适配器
                    .build();
            ApiService apiManager = retrofit.create(ApiService.class);//这里采用的是Java的动态代理模式
            apiManager.getNewsData("你的APIKREY", "10", page)
                    .subscribeOn(Schedulers.io())
                    .map(new Func1<NewsGson, List<News>>() {
                        @Override
                        public List<News> call(NewsGson newsgson) { //
                            List<News> newsList = new ArrayList<News>();
                            for (NewsGson.NewslistBean newslistBean : newsgson.getNewslist()) {
                                News new1 = new News();
                                new1.setTitle(newslistBean.getTitle());
                                new1.setCtime(newslistBean.getCtime());
                                new1.setDescription(newslistBean.getDescription());
                                new1.setPicUrl(newslistBean.getPicUrl());
                                new1.setUrl(newslistBean.getUrl());
                                newsList.add(new1);
                            }
                            return newsList; // 返回类型
                        }
                    })
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Subscriber<List<News>>() {
                        @Override
                        public void onNext(List<News> newsList) {
                            adapter.addAll(newsList);
                        }
    
                        @Override
                        public void onCompleted() {
                        }
    
                        @Override
                        public void onError(Throwable e) {
                            Toast.makeText(getContext(),
                                    "网络连接失败", Toast.LENGTH_LONG).show();
                        }
                    });
            page = page + 1;
        }
    
    1. 使用retrofit 发起网络请求
    2. 数据通过rxjava提交先在io线程里,返回到主线程
    3. 中间设置map 转换 把得到的Gson类转化为所需的News类(可以省略这一步)
    4. subscribe的onNext里处理返回的最终数据。

    关于建立Gson类

    Gson是谷歌的Json处理包,添加依赖。
    compile 'com.google.code.gson:gson:2.7'
    配合插件:GsonFormat可以快速通过json数据建立对应类。

    my.gif

    数据绑定到recyview

    由于我们使用的是被扩展的recyview,所以用起来很方便。
    具体使用去作者的githuaEasyRecyclerView

    1. Adapter
      继承recycle的adapter,主要返回自己的ViewHolder
    public class NewsAdapter extends RecyclerArrayAdapter<News> {
        public NewsAdapter(Context context) {
            super(context);
        }
    
        @Override
        public BaseViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) {
    
            return new NewsViewHolder(parent);
        }
    }
    
    1. ViewHolder
      public class NewsViewHolder extends BaseViewHolder<News> {
      private TextView mTv_name;
        private ImageView mImg_face;
        private TextView mTv_sign;
    
        public NewsViewHolder(ViewGroup parent) {
            super(parent,R.layout.news_recycler_item);
            mTv_name = $(R.id.person_name);
            mTv_sign = $(R.id.person_sign);
            mImg_face = $(R.id.person_face);    }
    
        @Override
        public void setData(final News data) {
            mTv_name.setText(data.getTitle());
            mTv_sign.setText(data.getCtime());
            Glide.with(getContext())
                    .load(data.getPicUrl())
                    .placeholder(R.mipmap.ic_launcher)
                    .centerCrop()
                    .into(mImg_face);
        }
    
    
    }
    

    3.设置recycleview

    recyclerView.setAdapter(adapter = new NewsAdapter(getActivity()));
            recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    
            //添加边框
            SpaceDecoration itemDecoration = new SpaceDecoration((int) PixUtil.convertDpToPixel(8, getContext()));
            itemDecoration.setPaddingEdgeSide(true);
            itemDecoration.setPaddingStart(true);
            itemDecoration.setPaddingHeaderFooter(false);
            recyclerView.addItemDecoration(itemDecoration);
    
            //更多加载
            adapter.setMore(R.layout.view_more, new RecyclerArrayAdapter.OnMoreListener() {
                @Override
                public void onMoreShow() {
                    getData();
                }
    
                @Override
                public void onMoreClick() {
    
                }
            });
            //写刷新事件
            recyclerView.setRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    recyclerView.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            adapter.clear();
                            page = 0;
                            getData();
                        }
                    }, 1000);
                }
            });
    
            //点击事件
            adapter.setOnItemClickListener(new RecyclerArrayAdapter.OnItemClickListener() {
                @Override
                public void onItemClick(int position) {
                    ArrayList<String> data = new ArrayList<String>();
                    data.add(adapter.getAllData().get(position).getPicUrl());
                    data.add(adapter.getAllData().get(position).getUrl());
                    Intent intent = new Intent(getActivity(), NewsDetailsActivity.class);
                    //用Bundle携带数据
                    Bundle bundle = new Bundle();
                    bundle.putStringArrayList("data", data);
                    intent.putExtras(bundle);
                    startActivity(intent);
                }
            });
    

    Glide网络图片加载库

    一个专注于平滑图片加载的库:

    依赖:
    compile 'com.github.bumptech.glide:glide:3.7.0'

    基本使用:

    Glide.with(mContext)
                    .load(path)
                    .asGif()
                    .override(300,300)
                    .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                    .placeholder(R.drawable.progressbar)
                    .thumbnail(1f)
                    .error(R.drawable.error)
                    .transform(new MyBitmapTransformation(mContext,10f))
                    .into(iv);
    
    

    新闻详情页

    布局:使用CoordinatorLayout实现上拉toolbar压缩动画。

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    
        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="256dp"
            android:fitsSystemWindows="true"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
    
            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/collapsing_toolbar"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true"
                app:contentScrim="?attr/colorPrimary"
                app:expandedTitleMarginEnd="64dp"
                app:expandedTitleMarginStart="48dp"
                app:layout_scrollFlags="scroll|exitUntilCollapsed">
    
                <ImageView
                    android:src="@mipmap/ic_launcher"
                    android:id="@+id/ivImage"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:fitsSystemWindows="true"
                    android:scaleType="centerCrop"
                    android:transitionName="新闻图片"
                    app:layout_collapseMode="parallax"
                    app:layout_collapseParallaxMultiplier="0.7" />
    
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    app:layout_collapseMode="pin"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
            </android.support.design.widget.CollapsingToolbarLayout>
        </android.support.design.widget.AppBarLayout>
    
        <android.support.v4.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
            <WebView
                android:id="@+id/web_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"></WebView>
        </android.support.v4.widget.NestedScrollView>
    </android.support.design.widget.CoordinatorLayout>
    

    CoordinatorLayout+AppBarLayout里面配合CollapsingToolbarLayout布局技能实现toolbar的动画:

    my.gif

    上面的Imgview加载图片,下面的webview加载文章内容

    public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_news_detail);
    
            toolbar.setTitle("新闻详情");
    
            setSupportActionBar(toolbar);
    //        设置返回箭头
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            toolbar.setNavigationOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    onBackPressed();
                }
            });
            //新页面接收数据
            Bundle bundle = this.getIntent().getExtras();
            //接收name值
            final ArrayList<String> data = bundle.getStringArrayList("data");
            Log.d("url", data.get(0));
    
            webText.setWebViewClient(new WebViewClient() {
    
                @Override
                public boolean shouldOverrideUrlLoading(WebView view, String url) {
                    // TODO Auto-generated method stub
                    view.loadUrl(url);
                    return true;
                }
            });
            webText.loadUrl(data.get(1));
    
            Glide.with(this)
                    .load(data.get(0)).error(R.mipmap.ic_launcher)
                    .fitCenter().into(ivImage);
    
        }
    

    到这里基本完成:最后动态添加fragment

    //菜单事件添加
    if (id == R.id.nav_camera) {
                // Handle the camera action
                NewsFragment fragment=new NewsFragment();
                FragmentManager fragmentManager=getSupportFragmentManager();
                FragmentTransaction transaction=fragmentManager.beginTransaction();
                transaction.replace(R.id.fragment_container,fragment);
                transaction.commit();
    
            } 
    

    效果测试:

    my.gif my.gif

    总结:

    只是勉强能用,还有很多细节没有优化。接下来好要继续学习。

    补充:关于ButterKnife的使用

    框架导入:
    搜索依赖butterknife导入:

    dependencies {
        compile fileTree(include: ['*.jar'], dir: 'libs')
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:24.2.0'
        //依赖添加
        compile 'com.jakewharton:butterknife:8.4.0'
    }
    

    使用步骤:
    注意我这里写的是8.40版本,和以前的有区别。
    如果的ButterKnife是8.01或者以上的话
    需要添加以下内容:
    1.classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.1.2'
            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    

    2.apply plugin: 'com.neenbedankt.android-apt'

    apply plugin: 'com.android.application'
    apply plugin: 'com.neenbedankt.android-apt'
    

    3.apt 'com.jakewharton:butterknife-compiler:8.4.0'

    dependencies {
        compile fileTree(include: ['*.jar'], dir: 'libs')
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:24.2.0'
        //依赖添加
        compile 'com.jakewharton:butterknife:8.4.0'
        apt 'com.jakewharton:butterknife-compiler:8.4.0'
    
    }
    

    Android Studio上方便使用butterknife注解框架的偷懒插件Android Butterknife Zelezny:

    my.gif

    技巧:鼠标要移动到布局文件名上。

    文件已发至Github---https://github.com/HuRuWo/YiLan

    关于接口调用失败,我测试发现天行数据失败。到后台发现原来是我的账号请求的次数超过上限了。(主要是大家用的都是我的账号,10000次请求分分钟就没了),所以各位可以自行申请账号。

    QQ截图20161125095311.png QQ图片20161125100452.png

    相关文章

      网友评论

      • 昔风不起唯有努力生存_465c:程序包com.bumptech.glide.request.animation不存在,这是什么意思怎么解决啊老板...
      • 0539c44157b1:《一个小时打造新闻app - 简书》写的挺不错的,已经收藏了。

        源码解析:http://sina.lt/fdcQ


        8afe04d66ee7:写的不错,谢谢博主;已 收 藏~
        8afe04d66ee7:写的蛮用心的,希望多多坚持那
      • lsys:不用解除订阅吗 照着你的GitHub敲了一遍 感觉运行很卡 是怎么回事啊
        五谷观精分道长:@梁sui先森 android是临时学着玩的,单纯应付了课设,现在去玩python了。
        lsys:@五谷观精分道长 大神又去做什么了呀 我感觉自己学安卓学不走:sweat: 完全没入门的样子
        五谷观精分道长:@梁sui先森 需要的 当初的版本不完整 没有继续更新 诸多bug还望谅解。我现在基本不弄安卓开发了。
      • 杨安大:咦,请问您用的是哪家的api?
      • d671b8961d19:请问这一句 “TextView mTv_name = $(R.id.person_name);” 是什么语法?
      • wanfengxixia:博主,网络获取失败。网络权限都有啊,求解? :octocat:
        wanfengxixia:Api过时了
      • Blazer:News 需要继承NewsGson吗?
      • Chandler_谦:中间设置map转换,把得到的Gson类转化为所需的News类,总是报错,map in Observable cannot be applied to anonymous rx.functions.Func1
      • 8b5e7c159a14:作者的依赖包在哪下的,能发一份吗
      • smallstrong:GsonFormat虽快,但是很多时候到解析的时候缺防不胜防。后台给的类型万一出错,懵逼的是你。全部string
      • 浮生如茶2016:其实类似look look用Android最新的技术来开发的牛逼APP还有很多,楼主有空可以看看我的文章:http://www.jianshu.com/p/34d643c6db3a,顺便提点意见,谢谢
      • 98720347038f:加油!!我是简书 官方专题 【大学生活】的主编,欢迎加我微信【sukie428】,来勾搭,我们还有个全国大学生群欢迎来玩。
      • 包哥:使用框架 呢个注册怎么添加啊 宝宝不懂啊
      • 8ba82d1e6e54:为什么我运行不了?
      • Champion是冠军:亮点呢?全是库的堆积而已
      • fjpengyu:改为MVP模式就更好了
        五谷观精分道长: @fjpengyu 那要加许多类了,一个简单的demo,还不至于用。
      • eb7cd5988749:你好,为什么我获取网络失败,求指教
        墨田:@stevenhjg 是不是网络权限没有添加
      • Waizau:as2.2 可以使用annotationProcessor 代替apt
      • 流星留步:蛮厉害的
      • GEM的紫领巾:已经很厉害了
      • 1fa748972469:as2.2 可以使用annotationProcessor 代替apt了
      • 谢尔顿:非常喜欢。准备看着你的打一遍,能看懂,但是自己就敲不出来了,谢谢分享 :blush:
        谢尔顿: @五谷观精分道长 ?
        谢尔顿:@FuckskyZhao 什么意思?? :anguished:
        五谷观精分道长: @gaojuanjuan 不知道怎么上的首页
      • 6c1e31444129:好厉害
      • 胡一凡:非常厉害
      • Maat红飞:非常棒
      • 郑明明:厉害呢,webAPP
        五谷观精分道长: @NtZheng 不算吧
      • 人鱼飞燕:看不懂-_-#
        superoidlau:@人鱼飞燕 学习一下retrofit就看懂了。 :blush:
        五谷观精分道长: @人鱼飞燕 第一次写博客
      • Caldremch:不错
      • fendo:赞一个?
      • 音乐记忆:厉害了,word哥..
      • 7d0a14cf1595:找到问题了 注解框架没有获取到控件id :sweat:
        7d0a14cf1595:@阿吹md 我自己的问题 我注解框架的依赖没添加完全。。。。。 :sweat_smile:
        阿吹md:@没有梦想的boy 我之前也遇到过,可能是一个bug吧,不知道怎么解决

      本文标题:一个小时打造新闻app

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