一个小时打造新闻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>