5. 首页模块(三)之首页

上一节对欢迎模块进行了综述(可参见 2. 欢迎模块 进行了解),接下来将从首页模块开始详细介绍:

知识点:

  • 掌握首页模块的开发,独立制作首页模块
  • 制作水平滑动广告栏
  • 第三方下拉刷新
  • 从服务器获取数据
  • ViewPager控件的使用
  • 事件捕获
  • 开启异步线程访问网络

首页:

任务综述:

在项目开发中,程序在经过欢迎界面后直接进入主界面,也就是首页界面。首页界面分为上下两部分,上部分通过ViewPager与Fragment实现滑动广告展示,下部分通过一个自定义的WrapRecyclerView控件展示新闻推荐信息。由于项目使用的是Tomcat搭建的一个小型服务器,因此首页界面的所有数据必须存放在Tomcat根目录的JSON文件中,并通过JSON文件获取数据填充界面。

1. 水平滑动广告栏界面

任务分析:
水平滑动广告栏主要用于广告信息或者活动信息,由ViewPager控件、TextView控件以及一个自定义的线性布局ViewPagerIndicator组成。

任务实施:
(1)创建水平滑动广告栏界面:main_adbanner.xml。
(2)放置界面控件。一个ViewPager控件用于显示左右滑动的广告图片,由于广告栏左下角的标题与右下角的小圆点都随着图片的滑动而发生变化,因此需要一个TextView控件与一个自定义的ViewPagerIndicator控件分别显示标题和小圆点。

main_adbanner.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/adbanner_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <android.support.v4.view.ViewPager
        android:id="@+id/slidingAdvertBanner"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginBottom="1dp"
        android:background="@android:color/black"
        android:gravity="center" />
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="#82000000">
        <TextView
            android:id="@+id/tv_advert_title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_weight="1"
            android:ellipsize="end"
            android:gravity="left|center_vertical"
            android:padding="4dp"
            android:singleLine="true"
            android:textColor="@android:color/white"
            android:textSize="14sp" />
        <com.itheima.topline.view.ViewPagerIndicator
            android:id="@+id/advert_indicator"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:layout_gravity="center_vertical"
            android:layout_marginRight="@dimen/activity_horizontal_margin"
            android:gravity="right|center_vertical"
            android:padding="4dp"/>
    </LinearLayout>
</RelativeLayout>

注意:
在上述文件中的Viewpager控件中,需要写出ViewPager的全路径。

(3)自定义ViewPagerIndicator控件。在实际开发中,很多时候Android自带的控件都不能满足用户的需求,此时需要一个控件。在此项目中,水平滑动广告栏底部的小圆点控件就需要通过自定义控件实现,因此可在com.XXXX.newsdemo下创建一个view包,然后在view包中创建一个ViewPagerIndicator类并继承LinearLayout类。

ViewPagerIndicator.java

public class ViewPagerIndicator extends LinearLayout {
    private int mCount; //小圆点的个数
    private int mIndex; //当前小圆点的位置
    private Context context;
    public ViewPagerIndicator(Context context) {
        this(context, null);
    }
    public ViewPagerIndicator(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }
    /**
     * 设置滑动到当前小圆点时其他圆点的位置
     */
    public void setCurrentPostion(int currentIndex) {
        mIndex = currentIndex; //当前小圆点
        this.removeAllViews(); //移除界面上存在的view
        int pex = context.getResources().getDimensionPixelSize(
                R.dimen.view_indicator_padding);
        for (int i = 0; i < this.mCount; i++) {
            //创建一个ImageView控件来放置小圆点
            ImageView imageView = new ImageView(context);
            if (mIndex == i) { //滑动到的当前界面
                //设置小圆点的图片为白色图片
                imageView.setImageResource(R.drawable.indicator_on);
            }else {
                //设置小圆点的图片为灰色图片
                imageView.setImageResource(R.drawable.indicator_off);
            }
            imageView.setPadding(pex, 0, pex, 0); //设置小圆点图片的上下左右的padding
            this.addView(imageView); //把此小圆点添加到自定义的ViewPagerIndicator控件上
        }
    }
    /**
     * 设置小圆点的数目
     */
    public void setCount(int count) {
        this.mCount = count;
    }
}

(4)修改dimens.xml文件。由于在ViewPagerIndicator类中使用到view_indicator_padding设置界面上圆点之间的距离,因此在res/values/dimens.xml中修改。

    <dimen name="view_indicator_padding">5dp</dimen>

(5)创建indicator_on.xml 和 indicator_off.xml文件。在自定义控件ViewPagerIndicator中分别有一个白色和一个灰色的小圆点图片,这两张图片是通过在drawable文件夹下分别创建indicator_on.xml 和 indicator_off.xml两个文件来实现的。

indicator_on.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <size android:height="6dp" android:width="6dp"/>
    <solid android:color="#E9E9E9"/>
</shape>

indicator_off.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <size android:height="6dp" android:width="6dp"/>
    <solid android:color="#BCBCBC"/>
</shape>

在上述代码中,shape用来设定形状,可以用于选择器和布局,shape默认为矩形(rectangle),可设置为椭圆形(oval)、线性形状(line)、环形(ring);size表示大小,可设置宽和高;soild表示内部填充色。

2. 首页界面

任务分析:
首页界面主要由水平滑动广告栏、四个学科按钮以及一个新闻列表组成,广告栏主要用于展示广告或活动信息,四个学科按钮分别是Python学科、Java学科、PHP学科、Android学科。新闻列表主要用于展示新闻信息。

任务实施:
(1)创建首页界面。由于首页界面分为两部分,一部分是滑动广告栏与学科按钮,另一部分是新闻列表,因此在res/layout下创建两个布局文件fragment_home.xml 与 head_view.xml 文件中,通过标签将main_adbanner.xml(广告栏)引入。

(2)导入界面图片(4个)。

(3)引入第三方下拉刷新。在实际开发者中,很多时候都需要展示一些比较炫酷的功能效果,如果在程序中直接开发,则代码量会大幅增加,也会损毁大量的开发时间,因此项目中的下拉刷新功能是通过引入第三方下拉刷新框架实现的。

显示效果如下:

下拉刷新

在AS中,选择File/New/Import Module选项把下拉刷新的框架导入项目,选择项目后右击选择Open Module Settings/Dependencies/“+”/Module Dependency选项,把下拉刷新加入主项目。

下拉刷新框架

(4)在fragment_home.xml文件中放置界面控件。
一个自定义的PullToRefreshView控件,用于显示下拉刷新;
一个自定义的WrapRecyelerView控件,用于显示新闻列表信息。

fragment_home.xml

<?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">
    <include layout="@layout/main_title_bar" />
    <com.itheima.PullToRefreshView
        android:id="@+id/pull_to_refresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#f6f6f6">
        <com.itheima.topline.view.WrapRecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:divider="@null"
            android:dividerHeight="0dp"
            android:fadingEdge="none" />
    </com.itheima.PullToRefreshView>
</LinearLayout>

(5)在head_view.xml文件中放置界面控件。
4个ImageView控件,用于显示4个学科所对应的控件;
4个Textiew控件,用于显示四个学科所对应的文字。

head_view.xml

<?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="@android:color/white"
    android:orientation="vertical">
    <include layout="@layout/main_adbanner" />
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginTop="8dp"
        android:orientation="horizontal">
        <LinearLayout
            android:id="@+id/ll_python"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">
            <ImageView
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:background="@drawable/python_icon" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:text="Python学科"
                android:textSize="14sp" />
        </LinearLayout>
        <LinearLayout
            android:id="@+id/ll_java"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">
            <ImageView
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:background="@drawable/java_icon" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:text="Java学科"
                android:textSize="14sp" />
        </LinearLayout>
        <LinearLayout
            android:id="@+id/ll_php"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">
            <ImageView
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:background="@drawable/php_icon" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:text="PHP学科"
                android:textSize="14sp" />
        </LinearLayout>
        <LinearLayout
            android:id="@+id/ll_android"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">
            <ImageView
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:background="@drawable/android_icon" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:text="Android学科"
                android:textSize="14sp" />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

显示效果如下:
4个学科

3. 自定义控件WrapRecyclerView

任务分析:
在此项目中,首页分为两部分,一部分是由广告栏与学科按钮组成的界面(头部界面);另一部分是一个新闻列表界面;若想把两部分结合起来,需要自定义一个WrapRecyclerView控件,用于展示新闻列表,然后调用自定义控件中添加头部界面的方法组成完整的首页界面。

任务实施:
(1)添加recyclerview-v7库。由于新闻列表用到recyclerview-v7包中的RecyclerView类,因此需要在AS中选中项目后右击Open Module Settings/Dependencies/“+”/Library dependency选项,然后找到com.android.support:recyclerview-v7库并添加到项目中。
(2)创建自定义控件WrapRecyclerView。在com.XXXX.newsdemo.view包中创建一个WrapRecyclerView类并继承RecyclerView类。

WrapRecyclerView.java

public class WrapRecyclerView extends RecyclerView {
    private WrapAdapter mWrapAdapter;
    private boolean shouldAdjustSpanSize;
    //临时头部View集合,用于存储没有设置Adapter之前添加的头部
    private ArrayList<View> mTmpHeaderView = new ArrayList<>();
    public WrapRecyclerView(Context context) {
        super(context);
    }
    public WrapRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public WrapRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    @Override
    public void setAdapter(Adapter adapter) {
        if (adapter instanceof WrapAdapter) {
            mWrapAdapter = (WrapAdapter) adapter;
            super.setAdapter(adapter);
        } else {
            mWrapAdapter = new WrapAdapter(adapter);
            for (View view : mTmpHeaderView) {
                mWrapAdapter.addHeaderView(view);
            }
            if (mTmpHeaderView.size() > 0) {
                mTmpHeaderView.clear();
            }
            super.setAdapter(mWrapAdapter);
        }
        if (shouldAdjustSpanSize) {
            mWrapAdapter.adjustSpanSize(this);
        }
        getWrappedAdapter().registerAdapterDataObserver(mDataObserver);
        mDataObserver.onChanged();
    }
    /**
     * Retrieves the previously set wrap adapter or null if no adapter is set.
     */
    @Override
    public WrapAdapter getAdapter() {
        return mWrapAdapter;
    }
    public Adapter getWrappedAdapter() {
        if (mWrapAdapter == null) {
            throw new IllegalStateException("You must set a adapter before!");
        }
        return mWrapAdapter.getWrappedAdapter();
    }
    /**
     * Adds a header view
     */
    public void addHeaderView(View view) {
        if (null == view) {
            throw new IllegalArgumentException("the view to add must not be null!");
        } else if (mWrapAdapter == null) {
            mTmpHeaderView.add(view);
        } else {
            mWrapAdapter.addHeaderView(view);
        }
    }
    @Override
    public void setLayoutManager(LayoutManager layout) {
        super.setLayoutManager(layout);
        if (layout instanceof GridLayoutManager || layout instanceof
                StaggeredGridLayoutManager){
            this.shouldAdjustSpanSize = true;
        }
    }
    private final AdapterDataObserver mDataObserver = new AdapterDataObserver() {
        @Override
        public void onChanged() {
            if (mWrapAdapter != null) {
                mWrapAdapter.notifyDataSetChanged();
            }
        }
        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            mWrapAdapter.notifyItemRangeInserted(positionStart, itemCount);
        }
        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount);
        }
        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            mWrapAdapter.notifyItemRangeRemoved(positionStart, itemCount);
        }
        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount)
        {
            mWrapAdapter.notifyItemMoved(fromPosition, toPosition);
        }
    };
}

(3)创建WrapAdapter类。由于自定义的WrapRecyclerView控件需要对添加的头部界面进行设置,因此需要在程序中创建一个adapter包,然后在adapter包中创建一个WrapAdapter类继承RecyclerView.Adapter<RecyclerView.ViewHolder>。

WrapAdapter.java

public class WrapAdapter<T extends RecyclerView.Adapter> extends
        RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private final T mRealAdapter;
    private boolean isStaggeredGrid;
    private static final int BASE_HEADER_VIEW_TYPE = -1 << 10;
    private ArrayList<FixedViewInfo> mHeaderViewInfos = new ArrayList<>();
    public class FixedViewInfo {
        public View view;
        public int viewType;
    }
    public WrapAdapter(T adapter) {
        super();
        mRealAdapter = adapter;
    }
    public T getWrappedAdapter() {
        return mRealAdapter;
    }
    public void addHeaderView(View view) {
        if (null == view) {
            throw new IllegalArgumentException("the view to add must not be null!");
        }
        final FixedViewInfo info = new FixedViewInfo();
        info.view = view;
        info.viewType = BASE_HEADER_VIEW_TYPE + mHeaderViewInfos.size();
        mHeaderViewInfos.add(info);
        notifyDataSetChanged();
    }
    public void adjustSpanSize(RecyclerView recycler) {
        if (recycler.getLayoutManager() instanceof GridLayoutManager) {
            final GridLayoutManager layoutManager = (GridLayoutManager)
                    recycler.getLayoutManager();
            layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    boolean isHeaderOrFooter =isHeaderPosition(position);
                    return isHeaderOrFooter ? layoutManager.getSpanCount() : 1;
                }
            });
        }
        if (recycler.getLayoutManager() instanceof StaggeredGridLayoutManager) {
            this.isStaggeredGrid = true;
        }
    }
    private boolean isHeader(int viewType) {
        return viewType >= BASE_HEADER_VIEW_TYPE
                && viewType < (BASE_HEADER_VIEW_TYPE + mHeaderViewInfos.size());
    }
    private boolean isHeaderPosition(int position) {
        return position < mHeaderViewInfos.size();
    }
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup,
                                                      int viewType) {
        if (isHeader(viewType)) {
            int whichHeader = Math.abs(viewType - BASE_HEADER_VIEW_TYPE);
            View headerView = mHeaderViewInfos.get(whichHeader).view;
            return createHeaderFooterViewHolder(headerView);
        } else {
            return mRealAdapter.onCreateViewHolder(viewGroup, viewType);
        }
    }
    private RecyclerView.ViewHolder createHeaderFooterViewHolder(View view) {
        if (isStaggeredGrid) {
            StaggeredGridLayoutManager.LayoutParams params = new
                    StaggeredGridLayoutManager.LayoutParams(
                    StaggeredGridLayoutManager.LayoutParams.MATCH_PARENT,
                    StaggeredGridLayoutManager.LayoutParams.WRAP_CONTENT);
            params.setFullSpan(true);
            view.setLayoutParams(params);
        }
        return new RecyclerView.ViewHolder(view) {
        };
    }
    @SuppressWarnings("unchecked")
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        if (position < mHeaderViewInfos.size()) {
        } else if (position < mHeaderViewInfos.size() + mRealAdapter.getItemCount())
        {
            mRealAdapter.onBindViewHolder(viewHolder,
                    position - mHeaderViewInfos.size());
        }
    }
    @Override
    public int getItemCount() {
        return mHeaderViewInfos.size() + mRealAdapter.getItemCount();
    }
    @Override
    public int getItemViewType(int position) {
        if (isHeaderPosition(position)) {
            return mHeaderViewInfos.get(position).viewType;
        } else {
            return mRealAdapter.getItemViewType(position - mHeaderViewInfos.size());
        }
    }
}

4. 首页界面Item

任务分析:
首页界面使用WrapRecyclerView控件展示新闻列表,因此需要创建一个该列表的Item界面。Item分为两种形式,一种是新闻类型,界面上显示一个新闻标题、一张新闻图片以及一个新闻类型;另一个是推荐类型,界面上显示三张推荐信息图片、一个推荐信息标题以及一个推荐类型。

首页界面Item

任务实施:
(1)创建首页界面Item:home_item_one.xml与home_item_two.xml。
(2)放置界面控件(home_item_one.xml)。
一个ImageView控件用于显示新闻图片;
两个TextView控件分别用于显示新闻名称与新闻类型。

home_item_one.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:layout_marginLeft="8dp"
    android:layout_marginRight="8dp"
    android:layout_marginTop="4dp"
    android:background="@drawable/item_bg_selector"
    android:padding="8dp">
    <ImageView
        android:id="@+id/iv_img"
        android:layout_width="100dp"
        android:layout_height="80dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_centerVertical="true"
        android:scaleType="fitXY" />
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@id/iv_img"
        android:orientation="vertical">
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="14sp" />
        <TextView
            android:id="@+id/tv_newsType_name"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:textSize="12sp" />
    </LinearLayout>
</RelativeLayout>

(3)放置home_item_two.xml文件中的控件。
3个ImageView控件用于显示推荐信息的图片;
2个ImageView控件分别用于显示新闻名称与新闻类型。

home_item_two.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="8dp"
    android:layout_marginRight="8dp"
    android:layout_marginTop="4dp"
    android:background="@drawable/item_bg_selector"
    android:orientation="vertical"
    android:padding="8dp">
    <TextView
        android:id="@+id/tv_name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/black"
        android:textSize="14sp" />
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="80dp"
        android:layout_centerVertical="true"
        android:layout_marginTop="8dp"
        android:layout_toRightOf="@id/iv_img"
        android:orientation="horizontal">
        <ImageView
            android:id="@+id/iv_img1"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:scaleType="fitXY" />
        <ImageView
            android:id="@+id/iv_img2"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_marginLeft="6dp"
            android:layout_weight="1"
            android:scaleType="fitXY" />
        <ImageView
            android:id="@+id/iv_img3"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_marginLeft="6dp"
            android:layout_weight="1"
            android:scaleType="fitXY" />
    </LinearLayout>
    <TextView
        android:id="@+id/tv_newsType_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textSize="12sp" />
</LinearLayout>

(4)创建Item界面的背景选择器。Item界面的背景的四个角是椭圆形的,并且在按下与弹起时,背景颜色会有明显的区别,这种效果可以通过背景选择器实现。
创建一个item_bg_selector.xml,根据按钮按下和弹起的状态变换它的背景颜色,给用户带来动态效果。当背景被按下时显示灰色(#fafafa),当背景弹起时显示白色(#ffffff)。

item_bg_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" >
        <shape android:shape="rectangle">
            <corners android:radius="5dp"/>
            <solid android:color="#fafafa"></solid>
        </shape>
    </item>
    <item android:state_pressed="false" >
        <shape android:shape="rectangle">
            <corners android:radius="5dp"/>
            <solid android:color="#ffffff" ></solid>
        </shape>
    </item>
</selector>

5. 创建NewsBean

任务分析:
由于首页的新闻信息包含新闻Id、新闻类型、新闻名称、新闻名称、跟贴数量、新闻图片1、新闻图片2、新闻图片3、新闻链接等属性,同时首页的广告栏信息包含广告栏Id、广告图片、广告标题、广告链接等属性,因此可以创建一个NewsBean类存放新闻信息和广告栏信息的属性。

任务实施:
创建bean包,在包中创建一个NewsBean类并实现Serializable接口。在该类中创建新闻信息与广告栏信息的所有属性。

NewsBean.java

public class NewsBean implements Serializable {
    private static final long serialVersionUID = 1L;
    private int id; //新闻Id
    //若type为1(黑马新闻)显示一张图片的布局,为2(黑马推荐)显示三张图片的布局
    private int type;
    private String newsName;      //新闻名称
    private String newsTypeName; //新闻类型,是黑马新闻还是黑马推荐
    private String img1;           //新闻图片1
    private String img2;      //新闻图片2
    private String img3;     //新闻图片3
    private String newsUrl; //新闻链接
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }
    public String getNewsTypeName() {
        return newsTypeName;
    }
    public void setNewsTypeName(String newsTypeName) {
        this.newsTypeName = newsTypeName;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getNewsName() {
        return newsName;
    }
    public void setNewsName(String newsName) {
        this.newsName = newsName;
    }
    public String getImg1() {
        return img1;
    }
    public void setImg1(String img1) {
        this.img1 = img1;
    }
    public String getImg2() {
        return img2;
    }
    public void setImg2(String img2) {
        this.img2 = img2;
    }
    public String getImg3() {
        return img3;
    }
    public void setImg3(String img3) {
        this.img3 = img3;
    }
    public String getNewsUrl() {
        return newsUrl;
    }
    public void setNewsUrl(String newsUrl) {
        this.newsUrl = newsUrl;
    }
}

6. 创建AdBannerFragment

任务分析:
由于首页界面的广告栏用到了Viewpager控件,因此创建一个AdBannerFragment类设置ViewPager控件中的数据。

任务实施:
(1)创建AdBannerFragment类。创建fragment包,在包中创建一个AdBannerfragment类并继承android.support.v4.app.Fragment类(AS自带了一种创建Fragment的方法,在Fragment创建后会默认重写多个无用的方法,因此为了方便起见,直接通过继承类的方式创建一个Fragment,重写所需方法)。

(2)添加图片加载框架glide-3.7.0.jar。在Project选项卡下的app中有一个libs文件夹,如果没有则新建一个,然后把glide-3.7.0.jar包复制libs文件夹中,选中glide-3.7.0.jar包,右击选择Add As Library选项,然后弹出一个对话框,把该jar包放在app的项目中即可。

(3)创建AdBannerFragment对应的视图。由于需要创建对应的视图,因此需要重写onCreateView()方法,在该方法中创建滑动广告栏的视图。

AdBannerFragment.java

public class AdBannerFragment extends Fragment {
    private NewsBean nb;   //广告
    private ImageView iv;  //图片
    public static AdBannerFragment newInstance(Bundle args) {
        AdBannerFragment af = new AdBannerFragment();
        af.setArguments(args);
        return af;
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle arg = getArguments();
        nb = (NewsBean) arg.getSerializable("ad"); //获取一个新闻对象
    }
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
    }
    @Override
    public void onResume() {
        super.onResume();
        if (nb != null) {
            //调用Glide框架加载图片
            Glide
                    .with(getActivity())
                    .load(nb.getImg1())
                    .error(R.mipmap.ic_launcher)
                    .into(iv);
        }
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        iv = new ImageView(getActivity());
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.FILL_PARENT,
                ViewGroup.LayoutParams.FILL_PARENT);
        iv.setLayoutParams(lp);                           //设置图片宽高参数
        iv.setScaleType(ImageView.ScaleType.FIT_XY); //把图片填满整个控件
        iv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
              
                /** 此处之后添加**/
                // if (nb == null) return;
                //Intent intent = new Intent(getActivity(), NewsDetailActivity.class);
                //intent.putExtra("newsBean", nb);
                //getActivity().startActivity(intent);
            }
        });
        return iv;
    }
}

7. 创建AdBannerAdapter

任务分析:
由于首页界面的广告栏用到了ViewPager控件,因此需要创建一个数据适配器AdBannerAdapter对ViewPager控件进行数据适配。

任务实施:
(1)创建AdBannerAdapter类。在adapter包中,创建一个AdBannerAdapter类继承FragmentStatePagerAdapter类并实现OnTouchListener接口。

(2)创建设置数据方法setData()。在AdBannerAdapter类中创建一个setData()方法,通过接收List集合设置界面数据。

AdBannerAdapter.java

public class AdBannerAdapter extends FragmentStatePagerAdapter implements
        View.OnTouchListener {
    private Handler mHandler;
    private List<NewsBean> abl;
    public AdBannerAdapter (FragmentManager fm, Handler handler) {
        super(fm);
        mHandler = handler;
        abl = new ArrayList<NewsBean>();
    }
    /**
     *  设置数据更新界面
     */
    public void setData(List<NewsBean> abl) {
        this.abl = abl;
        notifyDataSetChanged();
    }
    @Override
    public Fragment getItem(int index) {
        Bundle args = new Bundle();
        if (abl.size() > 0)
            args.putSerializable("ad", abl.get(index % abl.size()));
        return AdBannerFragment.newInstance(args);
    }
    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }
    /**
     * 返回数据集的真实容量大小
     */
    public int getSize() {
        return abl == null ? 0 : abl.size();
    }
    /**
     * 获取广告名称
     */
    public String getTitle(int index) {
        return abl == null ? null : abl.get(index).getNewsName();
    }
    @Override
    public int getItemPosition(Object object) {
        //防止刷新结果显示列表的时候出现缓存数据,重载这个函数,使之默认返回POSITION_NONE
        return POSITION_NONE;
    }
    @Override
    public boolean onTouch(View v, MotionEvent event) {
         /**此处之后添加**/
        // mHandler.removeMessages(HomeFragment.MSG_AD_SLID);
        return false;
    }
}

8. 首页界面Adapter

任务分析:
首页界面的新闻列表是通过WrapRecyclerView控件显示的,因此需要一个数据适配器HomeListAdapter对WrapRecyclerView控件进行数据适配。由于Item类型分为两种,因此需要在HomeListAdapter中根据新闻类型判断需要显示哪种类型的Item。

任务实施:
(1)创建HomeListAdapter。在adapter包中创建一个HomeAdapter类继承RecyclerView.Adapter<RecyclerView.ViewHolder>类,并重写onCreateViewHolder()、onBindViewHolder()、getItemViewType()、getItemCount()方法。在onCreateViewHolder()方法中根据新闻类型设置XML布局。

(2)创建TypeOneViewHolder类和TypeTwoViewType类。由于Item界面是根据新闻类型的不同而加载不同的布局文件的,因此需要在HomeListAdapter中分别创建一个TypeOneViewHolder类与TypeTwoViewHolder类用于获取两个界面上的控件。

HomeListAdapter.java

public class HomeListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private List<NewsBean> newsList;
    private static final int TYPE_ONE = 1; //一个图片的样式
    private static final int TYPE_TWO = 2; //三个图片的样式
    private Context context;
    public HomeListAdapter(Context context) {
        this.context = context;
    }
    public void setData(List<NewsBean> newsList) {
        this.newsList = newsList;
        notifyDataSetChanged();
    }
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int
            viewType){
        if (viewType == TYPE_TWO) {
            View view = LayoutInflater.from(viewGroup.getContext()).inflate(
                    R.layout.home_item_two, viewGroup, false);
            TypeTwoViewHolder viewHolder = new TypeTwoViewHolder(view);
            return viewHolder;
        } else {
            View view = LayoutInflater.from(viewGroup.getContext()).inflate(
                    R.layout.home_item_one, viewGroup, false);
            TypeOneViewHolder viewHolder = new TypeOneViewHolder(view);
            return viewHolder;
        }
    }
    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, int i) {
        if (newsList == null) return;
        final NewsBean bean = newsList.get(i);
        if (holder instanceof TypeOneViewHolder) {
            ((TypeOneViewHolder) holder).tv_name.setText(bean.getNewsName());
            ((TypeOneViewHolder) holder).tv_news_type_name.setText(
                    bean.getNewsTypeName());
            Glide
                    .with(context)
                    .load(bean.getImg1())
                    .error(R.mipmap.ic_launcher)
                    .into(((TypeOneViewHolder) holder).iv_img);
        } else if (holder instanceof TypeTwoViewHolder) {
            ((TypeTwoViewHolder) holder).tv_name.setText(bean.getNewsName());
            ((TypeTwoViewHolder) holder).tv_news_type_name.setText(
                    bean.getNewsTypeName());
            Glide
                    .with(context)
                    .load(bean.getImg1())
                    .error(R.mipmap.ic_launcher)
                    .into(((TypeTwoViewHolder) holder).iv_img1);
            Glide
                    .with(context)
                    .load(bean.getImg2())
                    .error(R.mipmap.ic_launcher)
                    .into(((TypeTwoViewHolder) holder).iv_img2);
            Glide
                    .with(context)
                    .load(bean.getImg3())
                    .error(R.mipmap.ic_launcher)
                    .into(((TypeTwoViewHolder) holder).iv_img3);
        }
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
              
               /**此处之后添加**/
               // Intent intent = new Intent(context, NewsDetailActivity.class);
                //intent.putExtra("newsBean", bean);
                //context.startActivity(intent);
            }
        });
    }
    @Override
    public int getItemViewType(int position) {
        if (1 == newsList.get(position).getType()) {
            return TYPE_ONE; //一个图片的类型
        } else if (2 == newsList.get(position).getType()) {
            return TYPE_TWO; //三个图片的类型
        } else {
            return TYPE_ONE;
        }
    }
    @Override
    public int getItemCount() {
        return newsList == null ? 0 : newsList.size();
    }
    public class TypeOneViewHolder extends RecyclerView.ViewHolder {
        public TextView tv_name, tv_news_type_name;
        public ImageView iv_img;
        public TypeOneViewHolder(View itemView) {
            super(itemView);
            tv_name = (TextView) itemView.findViewById(R.id.tv_name);
            tv_news_type_name = (TextView) itemView.findViewById(R.id.
                    tv_newsType_name);
            iv_img = (ImageView) itemView.findViewById(R.id.iv_img);
        }
    }
    public class TypeTwoViewHolder extends RecyclerView.ViewHolder {
        public TextView tv_name, tv_news_type_name;
        public ImageView iv_img1, iv_img2, iv_img3;
        public TypeTwoViewHolder(View itemView) {
            super(itemView);
            tv_name = (TextView) itemView.findViewById(R.id.tv_name);
            tv_news_type_name = (TextView) itemView.findViewById(R.id.
                    tv_newsType_name);
            iv_img1 = (ImageView) itemView.findViewById(R.id.iv_img1);
            iv_img2 = (ImageView) itemView.findViewById(R.id.iv_img2);
            iv_img3 = (ImageView) itemView.findViewById(R.id.iv_img3);
        }
    }
}

9. 首页界面逻辑代码

任务分析:
在首页界面中需要编写广告栏逻辑代码和新闻列表逻辑代码,由于广告栏每隔一段事件会自动切换到下一张图片,因此可以创建一个线程实现。广告栏和新闻列表的数据是从Tomcat服务器中获取,并通过JSON解析这些数据并加载到对应的界面。

任务实施:
(1)创建HomeFragment类。在fragment包中创建一个HomeFragment类。在该类中,创建界面控件的初始化方法initView(),在该方法中获取页面布局中需要用到的UI控件并初始化。

(2)添加okhttp库。由于在项目中需要使用OkHttpClient类向服务器请求数据,因此需要选中项目后右击选择Open Module Settings/Dependencies/“+”/Library dependency选项,然后找到com.squareup.okhttp:okhttp:2.0.0库并添加到项目中。

(3)从服务器获取数据。在HomeFragment中分别创建getADData()和getNewData()方法,用于从Tomcat服务器中获取广告栏与新闻的数据。

(4)广告栏自动滑动时间间隔。创建一个AdAutoSlidThread线程设置广告栏的滑动时间间隔。

HomeFragment.java

public class HomeFragment extends Fragment {
    private PullToRefreshView mPullToRefreshView;
    private WrapRecyclerView recycleView;
    public static final int REFRESH_DELAY = 1000;
    private ViewPager adPager;         //广告
    private ViewPagerIndicator vpi;  //小圆点
    private TextView tvAdName;        //广告名称
    private View adBannerLay;         //广告条容器
    private AdBannerAdapter ada; //适配器
    public static final int MSG_AD_SLID = 1;  //广告自动滑动
    public static final int MSG_AD_OK = 2;    //获取广告数据
    public static final int MSG_NEWS_OK = 3; //获取新闻数据
    private MHandler mHandler;                  //事件捕获
    private LinearLayout ll_python;
    private OkHttpClient okHttpClient;
    private HomeListAdapter adapter;
    private RelativeLayout rl_title_bar;
    public HomeFragment() {
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        okHttpClient = new OkHttpClient();
        mHandler = new MHandler();
        getADData();
        getNewsData();
        View view = initView(inflater, container);
        return view;
    }
    private View initView(LayoutInflater inflater, ViewGroup container) {
        View view = inflater.inflate(R.layout.fragment_home, container, false);
        rl_title_bar = (RelativeLayout) view.findViewById(R.id.title_bar);
        rl_title_bar.setVisibility(View.GONE);
        recycleView = (WrapRecyclerView) view.findViewById(R.id.recycler_view);
        recycleView.setLayoutManager(new LinearLayoutManager(getContext()));
        View headView = inflater.inflate(R.layout.head_view, container, false);
        recycleView.addHeaderView(headView);
        adapter = new HomeListAdapter(getActivity());
        recycleView.setAdapter(adapter);
        mPullToRefreshView = (PullToRefreshView) view.findViewById(
                R.id.pull_to_refresh);
        mPullToRefreshView.setOnRefreshListener(new PullToRefreshView.
                OnRefreshListener() {
            @Override
            public void onRefresh() {
                mPullToRefreshView.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mPullToRefreshView.setRefreshing(false);
                        getADData();
                        getNewsData();
                    }
                }, REFRESH_DELAY);
            }
        });
        mHandler = new MHandler();
        adBannerLay = headView.findViewById(R.id.adbanner_layout);
        adPager = (ViewPager) headView.findViewById(R.id.slidingAdvertBanner);
        vpi = (ViewPagerIndicator) headView.findViewById(R.id.advert_indicator);
        tvAdName = (TextView) headView.findViewById(R.id.tv_advert_title);
        ll_python = (LinearLayout) headView.findViewById(R.id.ll_python);
        adPager.setLongClickable(false);
        ada = new AdBannerAdapter(getActivity().getSupportFragmentManager(),mHandler);
        adPager.setAdapter(ada);
        adPager.setOnTouchListener(ada);
        adPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageSelected(int index) {
                if (ada.getSize() > 0) {
                    if (ada.getTitle(index % ada.getSize()) != null) {
                        tvAdName.setText(ada.getTitle(index % ada.getSize()));
                    }
                    vpi.setCurrentPostion(index % ada.getSize());
                }
            }
            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {
            }
            @Override
            public void onPageScrollStateChanged(int arg0) {
            }
        });
        resetSize();
        setListener();
        new AdAutoSlidThread().start();
        return view;
    }
    private void setListener() {
        ll_python.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(getActivity(), PythonActivity.class);
                startActivity(intent);
            }
        });
    }
    /**
     * 事件捕获
     */
    class MHandler extends Handler {
        @Override
        public void dispatchMessage(Message msg) {
            super.dispatchMessage(msg);
            switch (msg.what) {
                case MSG_AD_SLID:
                    if (ada.getCount() > 0) {
                        adPager.setCurrentItem(adPager.getCurrentItem() + 1);
                    }
                    break;
                case MSG_AD_OK:
                    if (msg.obj != null) {
                        String adResult = (String) msg.obj;
                        List<NewsBean> adl = JsonParse.getInstance().
                                getAdList(adResult);
                        if (adl != null) {
                            if (adl.size() > 0) {
                                ada.setData(adl);
                                tvAdName.setText(adl.get(0).getNewsName());
                                vpi.setCount(adl.size());
                                vpi.setCurrentPostion(0);
                            }
                        }
                    }
                    break;
                case MSG_NEWS_OK:
                    if (msg.obj != null) {
                        String newsResult = (String) msg.obj;
                        List<NewsBean> nbl = JsonParse.getInstance().
                                getNewsList(newsResult);
                        if (nbl != null) {
                            if (nbl.size() > 0) {
                                adapter.setData(nbl);
                            }
                        }
                    }
                    break;
            }
        }
    }
    class AdAutoSlidThread extends Thread {
        @Override
        public void run() {
            super.run();
            while (true) {
                try {
                    sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (mHandler != null)
                    mHandler.sendEmptyMessage(MSG_AD_SLID);
            }
        }
    }
    private void getNewsData() {
        Request request = new Request.Builder().url(Constant.WEB_SITE +
                Constant.REQUEST_NEWS_URL).build();
        Call call = okHttpClient.newCall(request);
        //开启异步线程访问网络
        call.enqueue(new Callback() {
            @Override
            public void onResponse(Response response) throws IOException {
                String res = response.body().string();
                Message msg = new Message();
                msg.what = MSG_NEWS_OK;
                msg.obj = res;
                mHandler.sendMessage(msg);
            }
            @Override
            public void onFailure(Request arg0, IOException arg1) {
            }
        });
    }
    private void getADData() {
        Request request = new Request.Builder().url(Constant.WEB_SITE +
                Constant.REQUEST_AD_URL).build();
        Call call = okHttpClient.newCall(request);
        //开启异步线程访问网络
        call.enqueue(new Callback() {
            @Override
            public void onResponse(Response response) throws IOException {
                String res = response.body().string();
                Message msg = new Message();
                msg.what = MSG_AD_OK;
                msg.obj = res;
                mHandler.sendMessage(msg);
            }
            @Override
            public void onFailure(Request arg0, IOException arg1) {
            }
        });
    }
    /**
     * 计算控件大小
     */
    private void resetSize() {
        int sw = UtilsHelper.getScreenWidth(getActivity());
        int adLheight = sw / 2; //广告条高度
        ViewGroup.LayoutParams adlp = adBannerLay.getLayoutParams();
        adlp.width = sw;
        adlp.height = adLheight;
        adBannerLay.setLayoutParams(adlp);
    }
}

(5)修改JsonParse.java文件。由于首页中服务器上获取的广告栏与新闻列表的数据不能直接加载到界面上,需要经过gson解析后才能显示在界面上,因此需要在utils包中的JsonParse中添加以下代码:

    public List<NewsBean> getAdList(String json) {
        //使用gson库解析JSON数据
        Gson gson = new Gson();
        //创建一个TypeToken的匿名子类对象,并调用对象的getType()方法
        Type listType = new TypeToken<List<NewsBean>>() {
        }.getType();
        //把获取到的信息集合存到adList中
        List<NewsBean> adList = gson.fromJson(json, listType);
        return adList;
    }
    public List<NewsBean> getNewsList(String json) {
        //使用gson库解析JSON数据
        Gson gson = new Gson();
        //创建一个TypeToken的匿名子类对象,并调用对象的getType()方法
        Type listType = new TypeToken<List<NewsBean>>() {
        }.getType();
        //把获取到的信息集合存到newsList中
        List<NewsBean> newsList = gson.fromJson(json, listType);
        return newsList;
    }

(6)修改AdBannerAdapter.java文件。当用户触摸广告栏时,广告栏上的图片需要停止自动切换,因此需要找到** 7. 创建AdBannerAdapter** 中的onTouch()方法,在该方法中添加如下代码:

mHandler.removeMessages(HomeFragment.MSG_AD_SLID);

(7)修改底部导航栏。由于在点击底部导航栏的首页按钮时会出现首页界面,因此需要在2. 欢迎模块的initView()方法中
的“viewPager = (ViewPager) findViewById(R.id.viewPager);”语句下方添加加载首页的代码。

MainActivty.java

        viewPager = (ViewPager) findViewById(R.id.viewPager);

        /**此处开始添加**/
        HomeFragment homeFragment = new HomeFragment();
        List<Fragment> alFragment = new ArrayList<Fragment>();
        alFragment.add(homeFragment);
      //ViewPager设置适配器
       viewPager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager(),
                alFragment));
        viewPager.setCurrentItem(0); //ViewPager显示第一个Fragment

(8)创建MyFragmentPagerAdapter.java文件。由于首页界面用到了ViewPager控件,因此需要给该控件设置一个适配器,在adapter文件夹中创建一个MyFragmentPagerAdapter类继承FragmentPagerAdapter类。

MyFragmentPagerAdapter.java

public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> list;
    public MyFragmentPagerAdapter(FragmentManager fm, List<Fragment> list) {
        super(fm);
        this.list = list;
    }
    @Override
    public Fragment getItem(int position) {
        return list.get(position);
    }
    @Override
    public int getCount() {
        return list.size();
    }
}

下载地址:
第三方下拉刷新
图片加载框架glide-3.7.0.jar

猜你喜欢

转载自blog.csdn.net/qq_41121204/article/details/84852162