1. 程式人生 > >仿網易新聞的頁面(ViewPager作為RecyclerView的Header)

仿網易新聞的頁面(ViewPager作為RecyclerView的Header)

需求

>
想實現一個仿網易新聞的頁面,上面是輪播的圖片,下面是 RecyclerView 顯示新聞列表。

錯誤方法

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ...>
    <ViewPager ... />

    <android.support.v7.widget.RecyclerView .../>

</LinearLayout>

這樣佈局 ViewPager 在 RecyclerView 的上面,如果不做特殊處理,當下滑 RecyclerView 載入更多內容的時候,ViewPager會固定不動。

正確的效果是下滑載入更多的時候,ViewPager 會滑出頁面,釋放空間供其他內容展示。

解決思路

方法有兩種

  • ViewPager作為 RecyclerView 的第0項,也就是 Header(本文采用該方法)
  • 利用ScrollView,重寫一些方法解決滑動衝突

總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">
<android.support.v7.widget.RecyclerView android:id="@+id/rcv_article_latest" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> </LinearLayout
>

很簡單,一個RecyclerView就行了

頭部 ViewPager 的viewholder_article_header.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">

    <!--ViewPager 熱門文章圖片展示-->
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="@color/gray_light">

        <android.support.v4.view.ViewPager
            android:id="@+id/vp_hottest"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorPrimary" />

        <LinearLayout
            android:id="@+id/ll_hottest_indicator"
            android:layout_width="wrap_content"
            android:layout_height="20dp"
            android:layout_gravity="bottom|right"
            android:layout_marginBottom="5dp"
            android:layout_marginRight="10dp"
            android:layout_marginTop="5dp"
            android:gravity="center"
            android:orientation="horizontal" />
    </FrameLayout>
</LinearLayout>

FrameLayout裡面的ViewPager和LinearLayout是覆蓋顯示的,實現在圖片的下方有個小圓點標記滑動到了第一張圖片。

新聞項 viewholder_article_item.xml 佈局

<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:fresco="http://schemas.android.com/apk/res-auto"
    android:id="@+id/cv_item"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="5dp"
    app:cardElevation="5dp"
    app:contentPadding="2dp">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <com.facebook.drawee.view.SimpleDraweeView
            android:id="@+id/rcv_article_photo"
            android:layout_width="100dp"
            android:layout_height="100dp"
            fresco:actualImageScaleType="centerInside"
            fresco:roundAsCircle="true"
            fresco:roundingBorderColor="@color/lightslategray"
            fresco:roundingBorderWidth="1dp" />


        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="vertical">

            <TextView
                android:id="@+id/rcv_article_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:layout_marginTop="2dp"
                android:gravity="center"
                android:text="關於舉辦《經典音樂作品欣賞與人文審美》講座的通知"
                android:textColor="@color/primary_text" />
            <!-- 新聞 釋出時間 來源 閱讀次數-->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                android:gravity="center"
                android:orientation="horizontal">

                <TextView
                    android:id="@+id/rcv_article_date"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="10dp"
                    android:layout_marginRight="2dp"
                    android:text="2015-01-09" />

                <TextView
                    android:id="@+id/rcv_article_source"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="2dp"
                    android:layout_marginRight="2dp"
                    android:text="科學研究院" />

                <TextView
                    android:id="@+id/rcv_article_readtimes"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="2dp"
                    android:layout_marginRight="2dp"
                    android:text="1129次" />

            </LinearLayout>


            <TextView
                android:id="@+id/rcv_article_preview"
                android:layout_width="wrap_content"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:layout_marginLeft="10dp"
                android:layout_marginTop="5dp"
                android:ellipsize="end"
                android:maxLines="2"
                android:text="講座主要內容:以中、西方音樂歷史中經典音樂作品為基礎,通過作曲家及作品創作背景、相關音樂文化史知識及音樂欣賞常識..." />

        </LinearLayout>
    </LinearLayout>

</android.support.v7.widget.CardView>

這篇文章 Android Material Design學習之RecyclerView代替 ListView http://blog.csdn.net/never_cxb/article/details/50495505實現了不加 ViewPager,利用 RecyclerView 展示新聞列表的功能。

RecyclerView 的介面卡

/**
 * 新聞列表的介面卡
 * 01-14 頭部是 ViewPager,下面是列表新聞
 * Created by tomchen on 1/11/16.
 */
public class ArticleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int TYPE_HEADER = 0;
    private static final int TYPE_ITEM = 1;

    //頭部固定為 張圖片
    private static final int NUM_IMAGE = 4;

    //Handler 用到的引數值
    private static final int UPTATE_VIEWPAGER = 0;

    //新聞列表
    private List<ItemArticle> articleList;

    //設定當前 第幾個圖片 被選中
    private int currentIndex = 0;

    //context
    private Context context;

    private LayoutInflater mLayoutInflater;

    private ImageView[] mCircleImages;//底部只是當前頁面的小圓點


    public ArticleAdapter(Context context, List<ItemArticle> articleList) {
        this.context = context;

        //頭部viewpager圖片固定是7張,剩下的是列表的資料
        this.articleList = articleList;
        mLayoutInflater = LayoutInflater.from(context);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //理論上應該把最可能返回的 TYPE 放在前面
        View view = null;

        if (viewType == TYPE_ITEM) {
            view = mLayoutInflater.inflate(
                    R.layout.viewholder_article_item, parent, false);
            return new ItemArticleViewHolder(view);
        }
        //頭部返回 ViewPager 實現的輪播圖片
        if (viewType == TYPE_HEADER) {
            view = mLayoutInflater.inflate(
                    R.layout.viewholder_article_header, parent, false);
            return new HeaderArticleViewHolder(view);
        }

        return null;
//        //可以丟擲異常,沒有對應的View型別
//        throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");

    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof ItemArticleViewHolder) {
            //轉型
            ItemArticleViewHolder newHolder = (ItemArticleViewHolder) holder;
            //注意RecyclerView第0項是 ViewPager 佔據了0 1 2 3圖片
            //那麼下面的列表展示是 RecyclerView 的第1項,從第4項開始
            ItemArticle article = articleList.get(position + NUM_IMAGE - 1);
            newHolder.rcvArticlePhoto.setImageURI(Uri.parse(article.getImageUrl()));
            newHolder.rcvArticleTitle.setText(article.getTitle());
            newHolder.rcvArticleDate.setText(article.getPublishDate());
            newHolder.rcvArticleSource.setText(article.getSource());
            //注意這個閱讀次數是 int 型別,需要轉化為 String 型別
            newHolder.rcvArticleReadtimes.setText(article.getReadTimes() + "次");
            newHolder.rcvArticlePreview.setText(article.getPreview());
        } else if (holder instanceof HeaderArticleViewHolder) {
            HeaderArticleViewHolder newHolder = (HeaderArticleViewHolder) holder;

            List<ItemArticle> headers = articleList.subList(0, NUM_IMAGE );
            HeaderImageAdapter imageAdapter = new HeaderImageAdapter(context, headers);

            setUpViewPager(newHolder.vpHottest, newHolder.llHottestIndicator, headers);

        }
    }


    private void setUpViewPager(final ViewPager vp, LinearLayout llBottom, final List<ItemArticle> headerArticles) {
        HeaderImageAdapter imageAdapter = new HeaderImageAdapter(context, headerArticles);
        //??這兒有些疑惑,Adapter 裡面巢狀設定 Adapter 是否優雅?
        vp.setAdapter(imageAdapter);

        final Handler mHandler = new Handler() {
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case UPTATE_VIEWPAGER:
                        if (msg.arg1 != 0) {
                            vp.setCurrentItem(msg.arg1);
                        } else {
                            //false 當從末頁調到首頁是,不顯示翻頁動畫效果,
                            vp.setCurrentItem(msg.arg1, false);
                        }
                        break;
                }
            }
        };

        //下面是設定動畫切換的樣式
        vp.setPageTransformer(true, new RotateUpTransformer());

        //建立底部指示位置的導航欄
        final ImageView[] mCircleImages = new ImageView[headerArticles.size()];

        for (int i = 0; i < mCircleImages.length; i++) {
            ImageView imageView = new ImageView(context);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(10, 10);
            params.setMargins(5, 0, 5, 0);
            imageView.setLayoutParams(params);
            if (i == 0) {
                imageView.setBackgroundResource(R.drawable.indicator_select);
            } else {
                imageView.setBackgroundResource(R.drawable.indicator_not_select);
            }

            mCircleImages[i] = imageView;
            //把指示作用的原點圖片加入底部的檢視中
            llBottom.addView(mCircleImages[i]);

        }

        vp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            //圖片左右滑動時候,將當前頁的圓點圖片設為選中狀態
            @Override
            public void onPageSelected(int position) {
                // 一定幾個圖片,幾個圓點,但注意是從0開始的
                int total = mCircleImages.length;
                for (int j = 0; j < total; j++) {
                    if (j == position) {
                        mCircleImages[j].setBackgroundResource(R.drawable.indicator_select);
                    } else {
                        mCircleImages[j].setBackgroundResource(R.drawable.indicator_not_select);
                    }
                }

                //設定全域性變數,currentIndex為選中圖示的 index
                currentIndex = position;
            }

            @Override
            public void onPageScrolled(int i, float v, int i1) {

            }

            @Override
            public void onPageScrollStateChanged(int state) {

                //實現切換到末尾後返回到第一張
                switch (state) {
                    // 手勢滑動
                    case ViewPager.SCROLL_STATE_DRAGGING:
                        break;

                    // 介面切換中
                    case ViewPager.SCROLL_STATE_SETTLING:
                        break;

                    case ViewPager.SCROLL_STATE_IDLE:// 滑動結束,即切換完畢或者載入完畢
                        // 當前為最後一張,此時從右向左滑,則切換到第一張
                        if (vp.getCurrentItem() == vp.getAdapter()
                                .getCount() - 1) {
                            vp.setCurrentItem(0, false);
                        }
                        // 當前為第一張,此時從左向右滑,則切換到最後一張
                        else if (vp.getCurrentItem() == 0) {
                            vp.setCurrentItem(vp.getAdapter()
                                    .getCount() - 1, false);
                        }
                        break;

                    default:
                        break;
                }
            }
        });


        //設定自動輪播圖片,5s後執行,週期是5s

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                Message message = new Message();
                message.what = UPTATE_VIEWPAGER;
                if (currentIndex == headerArticles.size() - 1) {
                    currentIndex = -1;
                }
                message.arg1 = currentIndex + 1;
                mHandler.sendMessage(message);
            }
        }, 6000, 6000);
    }

    @Override
    public int getItemCount() {
        //因為多了一個頭部,所以是+1,但是頭部 ViewPager 佔了7個
        //所以實際是少了6個
        return articleList.size() + 1 - NUM_IMAGE;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0)
            return TYPE_HEADER;
        else
            return TYPE_ITEM;
    }


    class HeaderArticleViewHolder extends RecyclerView.ViewHolder {

        //輪播的最熱新聞圖片
        @InjectView(R.id.vp_hottest)
        ViewPager vpHottest;
        //輪播圖片下面的小圓點
        @InjectView(R.id.ll_hottest_indicator)
        LinearLayout llHottestIndicator;

        //學院廣播資訊
        @InjectView(R.id.tv_college_broadcast)
        TextView tvCollegeBroadcast;

        public HeaderArticleViewHolder(View itemView) {
            super(itemView);
            ButterKnife.inject(this, itemView);
        }
    }

    class ItemArticleViewHolder extends RecyclerView.ViewHolder {

        @InjectView(R.id.rcv_article_photo)
        SimpleDraweeView rcvArticlePhoto;
        @InjectView(R.id.rcv_article_title)
        TextView rcvArticleTitle;
        @InjectView(R.id.rcv_article_date)
        TextView rcvArticleDate;
        @InjectView(R.id.rcv_article_source)
        TextView rcvArticleSource;
        @InjectView(R.id.rcv_article_readtimes)
        TextView rcvArticleReadtimes;
        @InjectView(R.id.rcv_article_preview)
        TextView rcvArticlePreview;

        public ItemArticleViewHolder(View itemView) {
            super(itemView);
            ButterKnife.inject(this, itemView);
        }
    }


}

ItemArticleViewHolder是列表展示的新聞項的 ViewHolder,對應了上面的 viewholder_article_item.xml。

HeaderArticleViewHolder 是頭部 ViewPager 的 ViewHolder, 對應viewholder_article_header.xml

Note

  • 本文上面的 ViewPager 輪播4幅圖片,所以getItemCount()需要複寫
  • List headers = articleList.subList(0, NUM_IMAGE );得到頭部圖片的資料
  • ItemArticle article = articleList.get(position + NUM_IMAGE - 1);得到下面新聞項的資料
  • getItemViewType(int position)根據position判斷是不是頭部ViewPager
  • onCreateViewHolder(ViewGroup parent, int viewType)根據viewType生成頭部圖片或者下面新聞項的ViewHolder

疑惑及後續計劃

參考文章