一个小时打造新闻app

BaiCasner 8年前
   <h2>前言</h2>    <p>作为一个新手,学完基础总想做点什么东西出来。于是我试着去模仿那些优秀的开源作品。</p>    <p>模仿作品: <a href="/misc/goto?guid=4959713607069066583" rel="nofollow,noindex">LookLook开源项目</a></p>    <p>经过一些波折和学习,写下模仿过程。</p>    <h2>一个小时打造新闻app</h2>    <p>实际上我花了大概三天才弄懂所有的东西,不过有了经验确实可以在一个小时里完成。</p>    <h2>使用框架</h2>    <p>rxjava和retrofit以及一个开源扩展的recyclerview和注解框架butterknife</p>    <p>集体依赖如下:</p>    <pre>  <code class="language-groovy">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'  }</code></pre>    <h2>开始制作app</h2>    <h2>界面制作</h2>    <p>新建项目,选择模板---->调整模板</p>    <p><img src="https://simg.open-open.com/show/221e2670b00f15bafa8710637acaec3b.png"></p>    <h3>菜单调整</h3>    <p>可以看到有menu里面两个文件</p>    <p>一个是主菜单,显示在Toobar上面</p>    <p>另一个是抽屉的菜单,按需修改即可。</p>    <p>activity_main_drawer.xml</p>    <pre>  <code class="language-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></code></pre>    <p>抽屉除了menu还有上面一部分,可以设置头像和签名。</p>    <p>nav_header_main.xml</p>    <pre>  <code class="language-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></code></pre>    <p>主界面大概这就可以了,剩下的就是要动态添加fragement到FragLayout里面去。</p>    <h2>数据获取</h2>    <p>首先新建fragment_news,布局文件只需要一个 EasyRecyclerView 即可</p>    <p>添加依赖</p>    <pre>  <code class="language-java">//高级的recyclerview      compile 'com.jude:easyrecyclerview:4.2.3'      compile 'com.android.support:recyclerview-v7:24.2.0'</code></pre>    <pre>  <code class="language-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="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></code></pre>    <h3>使用rxjava和retrofit获取json数据</h3>    <p>我的数据来自 <a href="/misc/goto?guid=4959722810950000040" rel="nofollow,noindex">天性数据</a> ,只需要注册即可获得APIKEY。</p>    <p>数据请求核心代码:</p>    <p>创建retrofit的请求接口</p>    <pre>  <code class="language-java">public interface ApiService{      @GET("social/")      Observable <NewsGson> getNewsData(@Query("key")String key,@Query("num") String num,@Query("page") int page);</code></pre>    <p>注意返回的是Gson数据而且设置为"被观察者"</p>    <p>数据获取函数:</p>    <pre>  <code class="language-java">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;      }</code></pre>    <ol>     <li>使用retrofit 发起网络请求</li>     <li>数据通过rxjava提交先在io线程里,返回到主线程</li>     <li>中间设置map 转换 把得到的Gson类转化为所需的News类(可以省略这一步)</li>     <li>subscribe的onNext里处理返回的最终数据。</li>    </ol>    <h3>关于建立Gson类</h3>    <p>Gson是谷歌的Json处理包,添加依赖。</p>    <p>compile 'com.google.code.gson:gson:2.7'</p>    <p>配合插件:GsonFormat可以快速通过json数据建立对应类。</p>    <p><img src="https://simg.open-open.com/show/e99e4cd3799f3afb1422a43136718ab6.gif"></p>    <h3>数据绑定到recyview</h3>    <p>由于我们使用的是被扩展的recyview,所以用起来很方便。</p>    <p>具体使用去作者的githua <a href="/misc/goto?guid=4959640208341528238" rel="nofollow,noindex">EasyRecyclerView</a></p>    <ol>     <li> <p>Adapter</p> <p>继承recycle的adapter,主要返回自己的ViewHolder</p> <pre>  <code class="language-java">public class NewsAdapter extends RecyclerArrayAdapter<News> {   public NewsAdapter(Context context) {       super(context);   }     @Override   public BaseViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) {         return new NewsViewHolder(parent);   }  }</code></pre> </li>     <li>ViewHolder</li>    </ol>    <pre>  <code class="language-java">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);      }      }</code></pre>    <p>3.设置recycleview</p>    <pre>  <code class="language-java">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);              }          });</code></pre>    <h3>Glide网络图片加载库</h3>    <p>一个专注于平滑图片加载的库:</p>    <p>依赖:</p>    <p>compile 'com.github.bumptech.glide:glide:3.7.0'</p>    <p>基本使用:</p>    <pre>  <code class="language-java">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);</code></pre>    <h2>新闻详情页</h2>    <p>布局:使用CoordinatorLayout实现上拉toolbar压缩动画。</p>    <pre>  <code class="language-java"><?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></code></pre>    <p>CoordinatorLayout+AppBarLayout里面配合CollapsingToolbarLayout布局技能实现toolbar的动画:</p>    <p><img src="https://simg.open-open.com/show/f190b74eda5fb0855fe31fc2192d982a.gif"></p>    <p>上面的Imgview加载图片,下面的webview加载文章内容</p>    <pre>  <code class="language-java">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);        }</code></pre>    <p>到这里基本完成:最后动态添加fragment</p>    <pre>  <code class="language-java">//菜单事件添加  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();            }</code></pre>    <p>效果测试:</p>    <p><img src="https://simg.open-open.com/show/b3a1cd28bef39b7b8817342d82a9c249.gif"></p>    <p>my.gif</p>    <p><img src="https://simg.open-open.com/show/96dd591eae77d85b676efd4c9d379ed7.gif"></p>    <h2>总结:</h2>    <p>只是勉强能用,还有很多细节没有优化。接下来好要继续学习。</p>    <h2>补充:关于ButterKnife的使用</h2>    <p>框架导入:</p>    <p>搜索依赖butterknife导入:</p>    <pre>  <code class="language-java">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'  }</code></pre>    <p>使用步骤:</p>    <p>注意我这里写的是8.40版本,和以前的有区别。</p>    <p>如果的ButterKnife是8.01或者以上的话</p>    <p>需要添加以下内容:</p>    <p>1. classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'</p>    <pre>  <code class="language-java">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      }  }</code></pre>    <p>2. apply plugin: 'com.neenbedankt.android-apt'</p>    <pre>  <code class="language-java">apply plugin: 'com.android.application'  apply plugin: 'com.neenbedankt.android-apt'</code></pre>    <p>3. apt 'com.jakewharton:butterknife-compiler:8.4.0'</p>    <pre>  <code class="language-java">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'    }</code></pre>    <p>Android Studio上方便使用butterknife注解框架的偷懒插件 Android Butterknife Zelezny :</p>    <p><img src="https://simg.open-open.com/show/9e8fa2f0e9f4b84f6053dfc06e67a356.gif"></p>    <p>技巧:鼠标要移动到布局文件名上。</p>    <p> </p>    <p> </p>