安卓專案實戰之ViewPager+RecycleView實現首頁分頁的導航選單
效果圖
用過美團和餓了麼的app的童鞋應該清楚這一功能。首頁選單可以分頁切換,類似我們的banner廣告切換效果,只不過只能手動切換。所以整個分頁效果,我們可以採用Viewpager實現,裡面的選單項我們則可以採用RecyclerView實現,動態改變裡面的選單項,所以今天我們這個首頁分頁選單效果,可以決定採用ViewPager+RecyclerView實現,效果如下:
轉載自:https://github.com/xiaohaibin/MeiTuanCategary
開始實現
1.首頁佈局檔案,程式碼如下,其中IndicatorView是自己自定義的小圓點指示器控制元件,也可以使用ViewPagerIndicator庫來實現。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/home_entrance" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <android.support.v4.view.ViewPager android:id="@+id/main_home_entrance_vp" android:layout_width="match_parent" android:layout_height="wrap_content"/> <com.stx.xhb.meituancategorydemo.widget.IndicatorView android:id="@+id/main_home_entrance_indicator" android:layout_width="match_parent" android:layout_height="32dp" android:layout_marginLeft="16dp" android:layout_gravity="bottom" android:layout_marginRight="16dp" app:gravity="0" app:indicatorColor="#668b8989" app:indicatorColorSelected="#FF5722" app:indicatorWidth="6"/> </LinearLayout>
2.由於我們分頁效果是以ViewPager實現的,所以我們要建立一個ViewPager的介面卡,CagegoryViewPagerAdapter.Class
public class CagegoryViewPagerAdapter extends PagerAdapter { private List<View> mViewList; public CagegoryViewPagerAdapter(List<View> mViewList) { this.mViewList = mViewList; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView(mViewList.get(position)); } @Override public Object instantiateItem(ViewGroup container, int position) { container.addView(mViewList.get(position)); return (mViewList.get(position)); } @Override public int getCount() { if (mViewList == null) return 0; return mViewList.size(); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } }
3.從上面的介面卡我們可以看到泛型為View,也就是每一個ViewPager頁面其實都是一個View例項,並且這個View的佈局也很簡單,僅僅包含一個RecycleView而已,程式碼如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
4.接下來就是RecyclerView的選單項的佈局檔案
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="6dp">
<ImageView
android:id="@+id/entrance_image"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_margin="2dp"
android:layout_weight="1"
android:scaleType="fitCenter"/>
<TextView
android:id="@+id/entrance_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:singleLine="true"
android:textColor="#80000000"
android:textSize="12dp"/>
</LinearLayout>
// 此處View的作用是選單點選時的背景變化效果
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/selector_trans_divider"/>
</FrameLayout>
selector_trans_divider.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"><color android:color="#20000000" />
</item>
<item android:state_pressed="false"><color android:color="@android:color/transparent" />
</item>
</selector>
5.由於我們的選單項有一個icon和名稱name,為了方便管理,我們可以建立一個選單項實體類ModelHomeEntrance.class
public class ModelHomeEntrance {
private String name = "";
private int image;
public ModelHomeEntrance(String name, int image) {
this.image = image;
this.name = name;
}
public int getImage() {
return image;
}
public String getName() {
return name;
}
}
6.建立一個RecyclerView的選單項列表介面卡,EntranceAdapter.Class
public class EntranceAdapter extends RecyclerView.Adapter<EntranceAdapter.EntranceViewHolder> {
private List<ModelHomeEntrance> mDatas;
/**
* 頁數下標,從0開始(通俗講第幾頁)
*/
private int mIndex;
/**
* 每頁顯示最大條目個數
*/
private int mPageSize;
private Context mContext;
private final LayoutInflater mLayoutInflater;
private List<ModelHomeEntrance> homeEntrances;
public EntranceAdapter(Context context, List<ModelHomeEntrance> datas, int index, int pageSize) {
this.mContext = context;
this.homeEntrances = datas;
mPageSize = pageSize;
mDatas = datas;
mIndex = index;
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public EntranceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new EntranceViewHolder(mLayoutInflater.inflate(R.layout.item_home_entrance, null));
}
@Override
public void onBindViewHolder(EntranceViewHolder holder, final int position) {
/**
* 在給View繫結顯示的資料時,計算正確的position = position + mIndex * mPageSize,
*/
final int pos = position + mIndex * mPageSize;
holder.entranceNameTextView.setText(homeEntrances.get(pos).getName());
holder.entranceIconImageView.setImageResource(homeEntrances.get(pos).getImage());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ModelHomeEntrance entrance = homeEntrances.get(pos);
// TODO: 2017/5/24 點選事件
}
});
}
@Override
public int getItemCount() {
return mDatas.size() > (mIndex + 1) * mPageSize ? mPageSize : (mDatas.size() - mIndex * mPageSize);
}
@Override
public long getItemId(int position) {
return position + mIndex * mPageSize;
}
class EntranceViewHolder extends RecyclerView.ViewHolder {
private TextView entranceNameTextView;
private ImageView entranceIconImageView;
public EntranceViewHolder(View itemView) {
super(itemView);
entranceIconImageView = (ImageView) itemView.findViewById(R.id.entrance_image);
entranceNameTextView = (TextView) itemView.findViewById(R.id.entrance_name);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (int) ((float) ScreenUtil.getScreenWidth() / 4.0f));
itemView.setLayoutParams(layoutParams);
}
}
}
7.最後就是我們的MainActivity的程式碼實現了,我們整體的思路其實就是需要根據首頁選單項的資料來源進行分頁顯示,首頁確定單頁選單顯示數量,總數除以單頁顯示數量取整就是顯示頁數,我們再根據頁數來建立RecyclerView將其新增到ViewPager的介面卡中,下面就讓我們一起來看看具體是如何的。
public class MainActivity extends AppCompatActivity {
public static final int HOME_ENTRANCE_PAGE_SIZE = 10;//首頁選單單頁顯示數量
private ViewPager entranceViewPager;
private LinearLayout homeEntranceLayout;
private List<ModelHomeEntrance> homeEntrances;
private IndicatorView entranceIndicatorView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initView();
init();
}
private void initView() {
homeEntranceLayout = (LinearLayout) findViewById(R.id.home_entrance);
entranceViewPager = (ViewPager) findViewById(R.id.main_home_entrance_vp);
entranceIndicatorView = (IndicatorView) findViewById(R.id.main_home_entrance_indicator);
}
private void initData() {
homeEntrances = new ArrayList<>();
homeEntrances.add(new ModelHomeEntrance("美食", R.mipmap.ic_category_0));
homeEntrances.add(new ModelHomeEntrance("電影", R.mipmap.ic_category_1));
homeEntrances.add(new ModelHomeEntrance("酒店住宿", R.mipmap.ic_category_2));
homeEntrances.add(new ModelHomeEntrance("生活服務", R.mipmap.ic_category_3));
homeEntrances.add(new ModelHomeEntrance("KTV", R.mipmap.ic_category_4));
homeEntrances.add(new ModelHomeEntrance("旅遊", R.mipmap.ic_category_5));
homeEntrances.add(new ModelHomeEntrance("學習培訓", R.mipmap.ic_category_6));
homeEntrances.add(new ModelHomeEntrance("汽車服務", R.mipmap.ic_category_7));
homeEntrances.add(new ModelHomeEntrance("攝影寫真", R.mipmap.ic_category_8));
homeEntrances.add(new ModelHomeEntrance("休閒娛樂", R.mipmap.ic_category_10));
homeEntrances.add(new ModelHomeEntrance("麗人", R.mipmap.ic_category_11));
homeEntrances.add(new ModelHomeEntrance("運動健身", R.mipmap.ic_category_12));
homeEntrances.add(new ModelHomeEntrance("大保健", R.mipmap.ic_category_13));
homeEntrances.add(new ModelHomeEntrance("團購", R.mipmap.ic_category_14));
homeEntrances.add(new ModelHomeEntrance("景點", R.mipmap.ic_category_16));
homeEntrances.add(new ModelHomeEntrance("全部分類", R.mipmap.ic_category_15));
}
private void init() {
LinearLayout.LayoutParams layoutParams12 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (int) ((float) ScreenUtil.getScreenWidth() / 2.0f));
//首頁選單分頁
FrameLayout.LayoutParams entrancelayoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, (int) ((float) ScreenUtil.getScreenWidth() / 2.0f + 70));
homeEntranceLayout.setLayoutParams(entrancelayoutParams);
entranceViewPager.setLayoutParams(layoutParams12);
LayoutInflater inflater = LayoutInflater.from(this);
//將RecyclerView放至ViewPager中:
int pageSize = HOME_ENTRANCE_PAGE_SIZE;
//一共的頁數等於 總數/每頁數量,並取整。
int pageCount = (int) Math.ceil(homeEntrances.size() * 1.0 / pageSize);
List<View> viewList = new ArrayList<View>();
for (int index = 0; index < pageCount; index++) {
//每個頁面都是inflate出一個新例項
RecyclerView recyclerView = (RecyclerView) inflater.inflate(R.layout.item_home_entrance_vp, entranceViewPager, false);
recyclerView.setLayoutParams(layoutParams12);
recyclerView.setLayoutManager(new GridLayoutManager(MainActivity.this, 5));
EntranceAdapter entranceAdapter = new EntranceAdapter(MainActivity.this, homeEntrances, index, HOME_ENTRANCE_PAGE_SIZE);
recyclerView.setAdapter(entranceAdapter);
viewList.add(recyclerView);
}
CagegoryViewPagerAdapter adapter = new CagegoryViewPagerAdapter(viewList);
entranceViewPager.setAdapter(adapter);
entranceIndicatorView.setIndicatorCount(entranceViewPager.getAdapter().getCount());
entranceIndicatorView.setCurrentIndicator(entranceViewPager.getCurrentItem());
entranceViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
entranceIndicatorView.setCurrentIndicator(position);
}
});
}
}
8.封裝的指示器類:
public class IndicatorView extends View {
private int indicatorColor = Color.rgb(0, 0, 0);
private int indicatorColorSelected = Color.rgb(0, 0, 0);
private int indicatorWidth = 0;
private int gravity = 0;
private int indicatorCount = 0;
private int currentIndicator = 0;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x12) {
invalidate();
}
}
};
public IndicatorView(Context context) {
super(context);
}
public IndicatorView(Context context, AttributeSet attrs) {
super(context, attrs);
if (attrs != null) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.IndicatorView);
indicatorColor = typedArray.getColor(R.styleable.IndicatorView_indicatorColor, Color.rgb(0, 0, 0));
indicatorColorSelected = typedArray.getColor(R.styleable.IndicatorView_indicatorColorSelected, Color.rgb(0, 0, 0));
indicatorWidth = ScreenUtil.dip2px(typedArray.getInt(R.styleable.IndicatorView_indicatorWidth, 0));
gravity = typedArray.getInt(R.styleable.IndicatorView_gravity, 0);
typedArray.recycle();
}
}
@Override
protected void onDraw(Canvas canvas) {
int viewWidth = getWidth();
int viewHeight = getHeight();
int totalWidth = indicatorWidth * (2 * indicatorCount - 1);
Paint paint = new Paint();
paint.setAntiAlias(true);
if (indicatorCount > 0) {
for (int i = 0; i < indicatorCount; i++) {
if (i == currentIndicator) {
paint.setColor(indicatorColorSelected);
} else {
paint.setColor(indicatorColor);
}
int left = (viewWidth - totalWidth) / 2 + (i * 2 * indicatorWidth);
switch (gravity) {
case 0:
left = (viewWidth - totalWidth) / 2 + (i * 2 * indicatorWidth);
break;
case 1:
left = i * 2 * indicatorWidth;
break;
case 2:
left = viewWidth - totalWidth + (i * 2 * indicatorWidth);
break;
}
int top = (viewHeight - indicatorWidth) / 2;
int right = left + indicatorWidth;
int bottom = top + indicatorWidth;
RectF rectF = new RectF(left, top, right, bottom);
canvas.drawOval(rectF, paint);
}
}
}
public void setIndicatorCount(int indicatorCount) {
this.indicatorCount = indicatorCount;
}
public void setCurrentIndicator(int currentIndicator) {
this.currentIndicator = currentIndicator;
handler.sendEmptyMessage(0x12);
}
}
自定義控制元件的自定義屬性,在values目錄下建立attrs.xml檔案,內容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="IndicatorView">
<attr name="indicatorColor" format="color"/>
<attr name="indicatorColorSelected" format="color"/>
<attr name="indicatorWidth" format="integer"/>
<attr name="gravity" format="integer"/>
</declare-styleable>
</resources>
9.工具類ScreenUtil
public class ScreenUtil {
static double scale;
static int screenWidth = 0, screenHeight = 0;
public static void init(Context context) {
scale = context.getResources().getDisplayMetrics().density;
screenWidth = context.getResources().getDisplayMetrics().widthPixels;
screenHeight = context.getResources().getDisplayMetrics().heightPixels;
}
public static int dip2px(float dipValue) {
return (int) (dipValue * scale + 0.5f);
}
public static int px2dip(float pxValue) {
return (int) (pxValue / scale + 0.5f);
}
public static int px2sp(float pxValue) {
return (int) (pxValue / scale + 0.5f);
}
public static int getScreenHeight() {
return screenHeight;
}
public static int getScreenWidth() {
return screenWidth;
}
}
10.工具類的使用:首先在MyApplication中做初始化:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
ScreenUtil.init(this);
}
}