Android项目实战之仿腾讯新闻客户端的部分功能实现
本项目的目的是通过实现模仿腾讯新闻客户端的部分功能来达到锻炼自己能力和验证前段时间学习Android的效果。
话不多说,直接上代码:
public class SplashActivity extends AppCompatActivity {
private RelativeLayout rootLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
rootLayout = (RelativeLayout) findViewById(R.id.rootLayout);
initAnimation();
}
/**
* 初始化动画
* 动画结束后启动主Activity
*/
private void initAnimation() {
AnimationSet animationSet = new AnimationSet(true);
Animation alphaAnimation = new AlphaAnimation(0,1);
alphaAnimation.setDuration(1000);
animationSet.setFillAfter(true);
Animation scaleAnimation = new ScaleAnimation(0.0f,1.0f,0.0f,1.0f,
ScaleAnimation.RELATIVE_TO_SELF,0.5f,
ScaleAnimation.RELATIVE_TO_SELF,0.5f);
scaleAnimation.setDuration(800);
scaleAnimation.setFillAfter(true);
animationSet.addAnimation(alphaAnimation);
animationSet.addAnimation(scaleAnimation);
rootLayout.startAnimation(animationSet);
animationSet.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
startActivity(new Intent(SplashActivity.this,MainActivity.class));
finish();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
}
上述代码非常简单,主要就是实现一个闪屏页的效果,使用了两个动画——ScaleAnimation(缩放动画)AlphaAnimation(透明度动画)来实现,与此同时最好是在设置完动画之后设置一个监听动画结束的监听器——Animation.AnimationListener来监听动画完成触发的动作。在此我设置了在动画结束之后启动MainActivity,并关闭SplashActivity
MainActivity的布局文件如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<cn.hylin.edu.szu.mynewsapp.view.NoScrollViewPager
android:id="@+id/viewPagerReplace"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<include layout="@layout/layout_footer" />
</LinearLayout>
layout/layout_footer代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background"
android:orientation="vertical">
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/rgItemSelector"
>
<RadioButton
android:id="@+id/rbNews"
android:checked="true"
style="@style/bottom_style"
android:drawableTop="@drawable/news_selector"
android:text="新闻"
/>
<RadioButton
android:id="@+id/rbVideo"
style="@style/bottom_style"
android:drawableTop="@drawable/video_selector"
android:text="视频"
/>
<RadioButton
android:id="@+id/rbRead"
style="@style/bottom_style"
android:drawableTop="@drawable/read_selector"
android:text="阅读"
/>
<RadioButton
android:id="@+id/rbLive"
style="@style/bottom_style"
android:drawableTop="@drawable/live_selector"
android:text="生活"
/>
</RadioGroup>
</LinearLayout>
上述两段代码主要是实现了MainActivity的布局,主步局使用一个自定义的View组件,该组件继承了ViewPager,并在实现中禁止了左右滑动事件,代码在下面展示,逐步局底部主要是用一个RadioGroup实现单选选项效果类似于微信客户端底部单选框。
public class NoScrollViewPager extends ViewPager {
public NoScrollViewPager(Context context) {
super(context);
}
public NoScrollViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
}
MainActivity实现如下:
public class MainActivity extends AppCompatActivity {
private NoScrollViewPager viewPagerReplace;
private RadioGroup rgItemSelector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPagerReplace = (NoScrollViewPager) findViewById(R.id.viewPagerReplace);
rgItemSelector = (RadioGroup) findViewById(R.id.rgItemSelector);
FragmentManager fm = getSupportFragmentManager();
final List<Fragment> fragments = new ArrayList<>();
fragments.add(new NewsFragment());
fragments.add(new VideoFragment());
fragments.add(new ReaderFragment());
fragments.add(new LiveFragment());
viewPagerReplace.setAdapter(new FragmentPagerAdapter(fm) {
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
});
rgItemSelector.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.rbNews:
viewPagerReplace.setCurrentItem(0);
break;
case R.id.rbVideo:
viewPagerReplace.setCurrentItem(1);
break;
case R.id.rbRead:
viewPagerReplace.setCurrentItem(2);
break;
case R.id.rbLive:
viewPagerReplace.setCurrentItem(3);
break;
}
}
});
}
}
MainActivity所实现的业务逻辑是非常简单的:在这里采用ViewPager + Fragment的方式管理四个标签的导航,分别实现切换到不同的标签(不同单选按钮选中)时显示不同的Fragment。
这篇的博客我先介绍以下我这次项目的最主要的部分——新闻模块
新闻模块主要采用Fragment内嵌套Fragment的方式,采用这用方式的原因是:我之前采用了Fragment + ViewPager来利用MainActivity管理了四个主要的模块Fragment,但是由于新闻模块还要继续细分成:头条、国内、国际、娱乐、体育等更细小的模块,所以我采用了TabLayout + Fragment嵌套的方式实现。代码如下:
布局代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/layout_head_content"/>
<android.support.v4.view.ViewPager
android:id="@+id/viewPagerTab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<?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:orientation="horizontal"
android:background="@color/colorPrimaryDark"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
>
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:tabIndicatorColor="@android:color/transparent"
app:tabMode="scrollable"
>
</android.support.design.widget.TabLayout>
</LinearLayout>
NewsFragment的业务实现代码如下:
public class NewsFragment extends Fragment {
private String[] tabs = new String[] {"头条","社会","国内","国际","娱乐","体育","军事","科技","财经","时尚"};
private ViewPager viewPagerTab;
private TabLayout tabLayout;
private View layoutView;
public NewsFragment() {
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
layoutView = inflater.inflate(R.layout.fragment_news_layout,null,false);
viewPagerTab = (ViewPager) layoutView.findViewById(R.id.viewPagerTab);
tabLayout = (TabLayout) layoutView.findViewById(R.id.tabLayout);
//初始化 Fragment
List<Fragment> fragments = new ArrayList<>();
for (int i = 0; i < tabs.length; i ++) {
Fragment fragment = new NewsTabFragment();
Bundle bundle = new Bundle();
bundle.putString("type", Constants.types[i]);
fragment.setArguments(bundle);
fragments.add(fragment);
}
//为ViewPager设置Adapter
viewPagerTab.setAdapter(new TabViewPagerAdapter(getChildFragmentManager(),tabs,getActivity(),fragments));
//绑定tab和viewPager
tabLayout.setupWithViewPager(viewPagerTab);
//设置分别的颜色(选择和未选择)
tabLayout.setTabTextColors(getResources().getColor(R.color.dark_white), Color.RED);
return layoutView;
}
}
从上述业务实现的代码可以看出,NewsFragment的业务逻辑主要就是初始化页面而已,通过给viewPager设置Adapter和给tabLayout绑定到viewPager实现导航效果。
具体每一个tab的Fragment怎么实现的我想只要介绍一个即可,因为在具体实现中是类似的不同的只是利用的查询字段不同而已。本次项目用的网络API是由聚合数据提供,具体怎么申请聚合数据的网络API可以在网上找到很多教程我就不多说了。
访问网络我使用了Retrofit这个网络访问框架来实现。关于具体的Retrofit的用法我想后面作为一个专题来介绍,在这里我主要就我用到的部分介绍以下。
首先定义一个业务接口:
public interface NewsService {
@GET("toutiao/index")
Call<NewsResponse> getResponse(@Query("key") String key, @Query("type") String type);
}
然后在访问网络的具体实现方法中利用Retrofit实现访问网络的业务逻辑,同时使用前面定义的接口,如下所示
/**
* 访问网络更新界面
*/
private void updateContent() {
Retrofit retrofit = new Retrofit.Builder().baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create()).build();
NewsService newsService = retrofit.create(NewsService.class);
Call<NewsResponse> call = newsService.getResponse(Constants.API_KEY, mText);
call.enqueue(new Callback<NewsResponse>() {
@Override
public void onResponse(Call<NewsResponse> call, Response<NewsResponse> response) {
if (response.isSuccessful()) {
NewsResponse newsResponse = response.body();
NewsResponse.NewsResult result = newsResponse.getResult();
data = result.getData();
list.clear();//清空列表
for (int i = 0; i < data.length; i++) {
list.add(data[i]);
}
adapter.notifyDataSetChanged();
refreshView.setRefreshing(false);
mProgressBar.setVisibility(View.INVISIBLE);
Toast.makeText(getActivity(), "刷新成功", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<NewsResponse> call, Throwable t) {
Toast.makeText(getActivity(), "获取数据失败,请检查网络设置", Toast.LENGTH_SHORT).show();
}
});
}
由于Retrofit访问网络就是在一个新线程中访问的,所以我们不需要在启动一个新线程来访问网络从而避免了使用android消息机制Handler,简化了编程。
上书代码中有一句:Retrofit retrofit = new Retrofit.Builder().baseUrl(Constants.BASE_URL).addConverterFactory(GsonConverterFactory.create()).build();
这行代码中使用了addConverterFactory(GsonConverterFactory.create()),这句的意思是在Retrofit访问完网络后得到的json数据采用Gson框架来解释。为此必须根据接口返回的json数据新建一个Bean类。所以根据接口的返回数据我实现了一个Bean类——NewsResponse;在访问成功之后Gson随即实现了解析数据并返回了NewsResponse实体对象。
由于新闻每一次加载的量有点大,所以在显示新闻条目时,我采用了ViewHolder机制来优化ListVIew,具体代码如下所示:
public class NewsListViewAdapter extends BaseAdapter {
private List<NewsResponse.NewsResponseBody> body;
private Context context;
public NewsListViewAdapter(List<NewsResponse.NewsResponseBody> body, Context context) {
this.body = body;
this.context = context;
}
@Override
public int getCount() {
return body.size();
}
@Override
public Object getItem(int position) {
return body.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = View.inflate(context, R.layout.listview_news_item_layout,null);
viewHolder.ivNewsLogo = (ImageView) convertView.findViewById(R.id.ivNewsLogo);
viewHolder.tvNewsTitle = (TextView) convertView.findViewById(R.id.tvNewsTitle);
viewHolder.tvNewsAuthor = (TextView) convertView.findViewById(R.id.tvNewsAuthor);
viewHolder.tvNewsTime = (TextView) convertView.findViewById(R.id.tvNewsTime);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.tvNewsTitle.setText(body.get(position).getTitle());
viewHolder.tvNewsAuthor.setText(body.get(position).getAuthor_name());
viewHolder.tvNewsTime.setText(body.get(position).getDate());
String imageUrl = body.get(position).getThumbnail_pic_s(); Glide.with(context).load(imageUrl).asBitmap().into(viewHolder.ivNewsLogo);
return convertView;
}
public static class ViewHolder {
ImageView ivNewsLogo ;
TextView tvNewsTitle ;
TextView tvNewsAuthor;
TextView tvNewsTime ;
}
}
实现的效果图如下:
以上便是本次介绍的内容,这是我第一次写博客,有很多不足的地方,希望热心朋友可以帮忙指正,内容仅作学习交流使用不做其他用途。