1. 程式人生 > >5. 首頁模組(三)之首頁

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