Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android使用RecyclerView和CardView,實現知乎日報精致布局

Android使用RecyclerView和CardView,實現知乎日報精致布局

編輯:關於Android編程

在寫博客園客戶端的時候,突然想到,弄個知乎日報風格的簡單清爽多好!不需要那麼多繁雜的信息干擾視野。先貼上效果圖,左邊是知乎日報的,右邊是本方案的

\\

本文所使用的ide是androidStudio

首先我們需要在項目中,引入RecyclerView、CardView

在build.gradle的 dependencies 添加兩條引用語句,如

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:22.0.0'
    compile 'com.android.support:cardview-v7:22.0.0'
    compile 'com.android.support:recyclerview-v7:22.0.0'
    compile 'com.android.support:support-v4:22.0.0'
}

先來簡單說下這兩個控件

RecyclerView

support-v7包中的新組件,是一個強大的滑動組件,與經典的ListView相比,同樣擁有item回收復用的功能,但是直接把viewholder的實現封裝起來,用戶只要實現自己的viewholder就可以了,該組件會自動幫你回收復用每一個item。它不但變得更精簡,也變得更加容易使用,而且更容易組合設計出自己需要的滑動布局。

我們先來看下,在調試裡面,開啟布局邊界選項,知乎日報是什麼樣子的

\

看到日期了沒,如果某條新聞比前面新聞要早一天,頭上就會出現日期。類似於按照日期分組的功能。

那麼在ListView裡,我們要實現這個功能,可能每個新聞裡面都要添加個文本控件,如果早一天,則顯示該控件。缺點不多說了,很丑陋

RecyclerView的強大之處在於,他將新聞要引用哪個布局的主動權,交給了新聞本身(而不是RecyclerView)。

也就是說,我可以有兩個布局文件,

新聞帶日期的

\

新聞不帶日期的

\

具體使用哪個布局,子類自己決定。

CardView

這個控件倒沒什麼特別要說的,布局類似FrameLayout,但是加了很多特效:卡片的邊框、陰影,duang,很酷、很炫。。

 

一點題外話:RecyclerView我之前接觸了好幾次,很想學,但是和ListView比起來,在很多功能上會有不同(這都是更先進的做法,吸取了ListView很多不足,不然谷歌也不會推出這個包),因此,如果你覺得熟練使用ListView就可以了,不需要了解RecyclerView的話,請相信我,花費的時間絕對值得!你可能會覺得使用ListView+CardView也能實現知乎日報的布局,但是拋開性能不說,CardView在ListView裡面是沒法使用布局屬性的!也就是說layout_margin是無效的。你的卡片就只能擠在一起了!

好,介紹完了,咱們就開始探索,如何實現知乎日報的風格吧!

前台布局工作

1:在界面上使用RecyclerView控件

    

2:准備不帶標題的CardView (fragment_base_swipe_list.xml)

裡面包了一個線性布局,左邊文字,右邊圖片




    

        

        
    

3:帶標題的CardView (fragment_base_swipe_group_item.xml)

我們使用include引入第二步不帶標題的CardView(復用),在此基礎上,添加了個TextView顯示日期




    

    

後台綁定工作

下面我們開始逐步介紹關鍵的後台綁定類NewsListAdapter.java

 

ListView有一堆各式各樣的Adapter可以綁定。那麼對應RecyclerView,它的Adpter很簡單,注意泛型參數必須繼承自ViewHolder。

public class NewsListAdapter extends RecyclerView.Adapter {

 

什麼是ViewHolder,官方是這樣解釋的:

對於RecyclerView裡面的某個元素,ViewHolder持有了該元素的布局和數據信息。我們實現ViewHolder時,最好可以添加一些屬性,來緩存一些需要花費資源處理的結果。

是不是很熟悉?ListView裡面,我們也有ViewHolder的,那套網上廣泛流傳的提高ListView性能的法則裡面,就有一個使用自定義ViewHolder來緩存元素的布局信息以提高性能。

不記得啦?看看下面的代碼吧

\

RecyclerView的強大之處就在於,它本身就提供了ViewHolder,我們只要繼承自該ViewHolder就可以了,至於ViewHolder怎麼存儲,系統會自動幫我們搞定。

 

OK,解釋清楚了什麼是ViewHolder,接下來是咱自己實現的新聞、帶標題的新聞ViewHolder

 /**
     * 新聞標題
     */
    public class NormalItemHolder extends RecyclerView.ViewHolder {
        TextView newsTitle;
        ImageView newsIcon;

        public NormalItemHolder(View itemView) {
            super(itemView);
            newsTitle = (TextView) itemView.findViewById(R.id.base_swipe_item_title);
            newsIcon = (ImageView) itemView.findViewById(R.id.base_swipe_item_icon);
            itemView.findViewById(R.id.base_swipe_item_container).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    showNewsDetail(getPosition());
                }
            });
        }
    }

    /**
     * 帶日期新聞標題
     */
    public class GroupItemHolder extends NormalItemHolder {
        TextView newsTime;

        public GroupItemHolder(View itemView) {
            super(itemView);
            newsTime = (TextView) itemView.findViewById(R.id.base_swipe_group_item_time);
        }
    }

 

ViewHolder聲明好了,那麼Adapter如何知道該使用哪種呢?

我們重寫父類RecyclerView.Adapter的兩個方法就好了。

1:生成一個標志

 /**
     * 決定元素的布局使用哪種類型
     * @param position 數據源List的下標
     * @return 一個int型標志,傳遞給onCreateViewHolder的第二個參數
     */
    @Override
    public int getItemViewType(int position) {

2:根據第一步的標志調用對應的ViewHolder

 /**
     * 渲染具體的ViewHolder
     * @param viewGroup ViewHolder的容器
     * @param i 一個標志,我們根據該標志可以實現渲染不同類型的ViewHolder
     * @return 
     */
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {

綁定ViewHolder的數據

重寫父類的綁定方法就好了。

/**
     * 綁定ViewHolder的數據。
     * @param viewHolder 
     * @param i 數據源list的下標
     */
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i)

 

上面所述關鍵的三個父類方法,重寫後就可以達到我們要求了,下面貼上完整的代碼,構造方法有兩個參數:activity,數據源List

供各位參考

package zhexian.app.zoschina.news;

import android.support.v7.widget.RecyclerView;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

import zhexian.app.zoschina.R;
import zhexian.app.zoschina.base.BaseActionBarActivity;
import zhexian.app.zoschina.lib.ZImage;
import zhexian.app.zoschina.util.ConfigConstant;


public class NewsListAdapter extends RecyclerView.Adapter {
    private static final int NORMAL_ITEM = 0;
    private static final int GROUP_ITEM = 1;

    private BaseActionBarActivity mContext;
    private List mDataList;
    private LayoutInflater mLayoutInflater;

    public NewsListAdapter(BaseActionBarActivity mContext, List mDataList) {
        this.mContext = mContext;
        this.mDataList = mDataList;
        mLayoutInflater = LayoutInflater.from(mContext);
    }

    /**
     * 渲染具體的ViewHolder
     * @param viewGroup ViewHolder的容器
     * @param i 一個標志,我們根據該標志可以實現渲染不同類型的ViewHolder
     * @return
     */
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {

        if (i == NORMAL_ITEM) {
            return new NormalItemHolder(mLayoutInflater.inflate(R.layout.fragment_base_swipe_item, viewGroup, false));
        } else {
            return new GroupItemHolder(mLayoutInflater.inflate(R.layout.fragment_base_swipe_group_item, viewGroup, false));
        }
    }

    /**
     * 綁定ViewHolder的數據。
     * @param viewHolder
     * @param i 數據源list的下標
     */
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
        NewsListEntity entity = mDataList.get(i);

        if (null == entity)
            return;

        if (viewHolder instanceof GroupItemHolder) {
            bindGroupItem(entity, (GroupItemHolder) viewHolder);
        } else {
            NormalItemHolder holder = (NormalItemHolder) viewHolder;
            bindNormalItem(entity, holder.newsTitle, holder.newsIcon);
        }
    }

    @Override
    public int getItemCount() {
        return mDataList.size();
    }

    /**
     * 決定元素的布局使用哪種類型
     * @param position 數據源List的下標
     * @return 一個int型標志,傳遞給onCreateViewHolder的第二個參數
     */
    @Override
    public int getItemViewType(int position) {
        //第一個要顯示時間
        if (position == 0)
            return GROUP_ITEM;

        String currentDate = mDataList.get(position).getPublishDate();
        int prevIndex = position - 1;
        boolean isDifferent = !mDataList.get(prevIndex).getPublishDate().equals(currentDate);
        return isDifferent ? GROUP_ITEM : NORMAL_ITEM;
    }

    @Override
    public long getItemId(int position) {
        return mDataList.get(position).getNewsID();
    }

    void bindNormalItem(NewsListEntity entity, TextView newsTitle, ImageView newsIcon) {
        if (entity.getIconUrl().isEmpty()) {

            if (newsIcon.getVisibility() != View.GONE)
                newsIcon.setVisibility(View.GONE);
        } else {
            ZImage.getInstance().load(entity.getIconUrl(), newsIcon,
                    ConfigConstant.LIST_ITEM_IMAGE_SIZE_DP, ConfigConstant.LIST_ITEM_IMAGE_SIZE_DP, true, mContext.getMyApplication().canRequestImage());

            if (newsIcon.getVisibility() != View.VISIBLE)
                newsIcon.setVisibility(View.VISIBLE);
        }
        newsTitle.setText(Html.fromHtml(entity.getTitle()));
    }

    void bindGroupItem(NewsListEntity entity, GroupItemHolder holder) {
        bindNormalItem(entity, holder.newsTitle, holder.newsIcon);
        holder.newsTime.setText(entity.getPublishDate());
    }

    void showNewsDetail(int pos) {
        NewsListEntity entity = mDataList.get(pos);
        NewsDetailActivity.actionStart(mContext, entity.getNewsID(), entity.getRecommendAmount(), entity.getCommentAmount());
    }

    /**
     * 新聞標題
     */
    public class NormalItemHolder extends RecyclerView.ViewHolder {
        TextView newsTitle;
        ImageView newsIcon;

        public NormalItemHolder(View itemView) {
            super(itemView);
            newsTitle = (TextView) itemView.findViewById(R.id.base_swipe_item_title);
            newsIcon = (ImageView) itemView.findViewById(R.id.base_swipe_item_icon);
            itemView.findViewById(R.id.base_swipe_item_container).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    showNewsDetail(getPosition());
                }
            });
        }
    }

    /**
     * 帶日期新聞標題
     */
    public class GroupItemHolder extends NormalItemHolder {
        TextView newsTime;

        public GroupItemHolder(View itemView) {
            super(itemView);
            newsTime = (TextView) itemView.findViewById(R.id.base_swipe_group_item_time);
        }
    }
}

 

ps,咱在代碼裡面,將日期格式統一轉換成友好格式(今日、昨日、6月6日星期6),在數據綁定的時候,和前面一條對比,如果不一樣,則使用帶日期的格式。就這樣。

奉上日期友好格式生成代碼

 public static int daysOfTwo(Date originalDate, Date compareDateDate) {
        Calendar aCalendar = Calendar.getInstance();
        aCalendar.setTime(originalDate);
        int originalDay = aCalendar.get(Calendar.DAY_OF_YEAR);
        aCalendar.setTime(compareDateDate);
        int compareDay = aCalendar.get(Calendar.DAY_OF_YEAR);

        return originalDay - compareDay;
    }

    public static String FriendlyDate(Date compareDate) {
        Date nowDate = new Date();
        int dayDiff = daysOfTwo(nowDate, compareDate);

        if (dayDiff <= 0)
            return "今日";
        else if (dayDiff == 1)
            return "昨日";
        else if (dayDiff == 2)
            return "前日";
        else
            return new SimpleDateFormat("M月d日 E").format(compareDate);
    }
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved