Android项目实战之仿腾讯新闻客户端的部分功能实现

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 ;
    }
}

实现的效果图如下:
这里写图片描述
这里写图片描述

以上便是本次介绍的内容,这是我第一次写博客,有很多不足的地方,希望热心朋友可以帮忙指正,内容仅作学习交流使用不做其他用途。

评论 40
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值