Android UI 之 RecyclerView實現常見首頁佈局
一丶效果演示
實現功能:
1.底部tab欄,切換“首頁”,“我的”
2.頂部banner,APP常見首頁用於展示活動詳情或者近期熱門,這裡用來展示個人“CSDN” “簡書” “github”
3.中間選單欄,導航APP各模組,這裡作為後面將自學的內容模組,“Java基礎”,"Android UI 源生控制元件",
“RecyclerView”(單獨做一個模組),“MyView 自定義View”,“實戰專案Demo提取”。
4.下面,內容概述欄,簡要介紹各模組包含內容,這裡只做了簡單文字介紹,APP裡通常也會有佈局資料展示
5. 下拉重新整理
二丶慨述
1.最近時間的學習內容是:Android UI ,其實從剛開始接觸Android,還有實習那會,接觸最多的就是UI,相關工作最多的也是UI。剛接觸專案,網路框架,三方運用這些專案組的大神已經搭好選好,分配在手裡的任務,往往是UI頁面實現,後臺資料處理和簡單邏輯的處理,Android UI 可以說是Android入門很重要的部分。
2.也說說最近熱門Kotlin,剛複習完Java基礎,新的Android 第一語言就換了,花了差不多3天時間追熱度就停了:
1).語法不同,如果專案不用,還是會忘;
2).目前沒有新專案,換用Kotlin機率小,從Android技術普及的速度看,至少也得二三年(成為大神時間上的小目標)才會比Java運用率高。
3.5.0新特性很早就出來了,記得在去年改一個RecyclerView相容問題,米2,沒找到原因,最後解決方案是把RecyclerView換成了ListView,但如今,5.0提供的原生控制元件已經是主流,這裡Android UI 主要就是複習5.0後的控制元件組合使用,以及自定義控制元件的使用。
4.同學讀研,專業定向輸出產品經理,大學都還沒開Android課,高校研究生就開始培養產品經理了,學長畢業工資20K(深圳),剛聽到這個是相當不服,理論上產品經理根據需求設計產品,程式設計師開發。但優秀的程式設計師搶產品飯碗呢,絕對是效率更高。有時測試的工資都比開發高,程式設計師沒兼職測試?怪自己還不是大神咯,努力快速成長,成為大神。
三丶準備工作
1.RecyclerView基礎知識,鴻神部落格:
2.RecyclerView實現多佈局相關知識
覺得視訊版學的慢想要原始碼,部落格版也為你找好
以上準備工作做好了,相信你已不再覺得用RecyclerView實現首頁佈局是什麼難度了,剩下的就是找控制元件實現
四丶正文詳解
1.先說首頁佈局HomeFragment的home_fragment.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="match_parent" android:background="@color/white" android:orientation="vertical"> <android.support.design.widget.CoordinatorLayout android:layout_below="@+id/title_home" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <include layout="@layout/home_content_main" /> </android.support.design.widget.CoordinatorLayout> <TextView android:id="@+id/title_home" android:layout_width="match_parent" android:layout_height="@dimen/title_bar_height" android:textSize="18sp" android:gravity="center" android:background="@color/title_colcor" android:textColor="@color/black" android:text="首頁"/> </RelativeLayout>
這個CoordinatorLayout Android UI 部分在做詳解
看home_comtent_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/swipe_refresh_layout" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" > <android.support.v7.widget.RecyclerView android:id="@+id/lv_home_listview" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.v4.widget.SwipeRefreshLayout>這個佈局在開發中經常用到,SwipeRefreshLayout用於下拉重新整理,其餘的交給RecyclerView。
2.HomeFragment的程式碼片段,這裡註釋講解SwipeRefreshLayout和RecyclerView的基本用法
public void initView () { mSwipeRefreshLayout = (SwipeRefreshLayout) getActivity().findViewById(R.id.swipe_refresh_layout); //★1.設定重新整理時動畫的顏色,可以設定4個 mSwipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary, R.color.blueLight, R.color.btn_cm_bg_pressed, R.color.withe); //★2.設定重新整理監聽事件 mSwipeRefreshLayout.setOnRefreshListener(this); //RecyclerView簡單步驟 homeAdapter = new HomeAdapter(getActivity(), mEntities); mRecyclerView = (RecyclerView) getActivity().findViewById(R.id.lv_home_listview); LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); mRecyclerView.setLayoutManager(layoutManager); mRecyclerView.setAdapter(homeAdapter); } @Override public void onRefresh() { new Handler().postDelayed(new Runnable() { @Override public void run() { mEntities.clear(); intData(); //★3.通知recycleView改變了資料 homeAdapter.notifyDataSetChanged(); //★4.記得關閉重新整理,否則重新整理球一直在轉 mSwipeRefreshLayout.setRefreshing(false); } }, 50); }
SwipeRefreshLayout,簡單運用就是這個樣,intData可以當做是網路請求,這裡我做的是資料的人工處理。
3.好了,下面的就是實現多佈局RecyclerView的adapter
/** * <pre> * author : JinBiao * CSDN : http://my.csdn.net/DT235201314 * time : 2017/05/31 * desc : RecyclerView實現首頁adapter * version: 1.0 * </pre> */ public class HomeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private final Context context; //佈局標識集合 private final List<AbsBaseEntity> typeList; private LayoutInflater mInflater; //設定兩個常量 private final int TYPE_BANNER = 0; //廣告位 private final int TYPE_MENU = 1; //選單 private final int TYPE_JAVA = 2; //JavaDemo 頁面 private final int TYPE_ANDRIOD_UI = 3; //Android UI 頁面 private final int TYPE_RECYCLERVIEW = 4; //RecyclerViewDemo 頁面 private final int TYPE_MYVIEW = 5; //自定義View 頁面 public HomeAdapter(Context context, List<AbsBaseEntity> typeList) { this.context = context; this.typeList = typeList; this.mInflater = LayoutInflater.from(context); } @Override public int getItemViewType(int position) { AbsBaseEntity entity = typeList.get(position); if (entity instanceof BannerEntity) { return TYPE_BANNER; } else if (entity instanceof MenuEntity) { return TYPE_MENU; } else if (entity instanceof JavaEntity) { return TYPE_JAVA; } else if (entity instanceof AndroidUIEntity) { return TYPE_ANDRIOD_UI; } else if (entity instanceof RecyclerViewEntity) { return TYPE_RECYCLERVIEW; } else if (entity instanceof MyViewEntity) { return TYPE_MYVIEW; } else { return 0; } } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case TYPE_BANNER: //廣告 return new BannerItemViewHolder(context, mInflater.inflate(R.layout.home_banner_view, parent, false)); case TYPE_MENU: //選單 return new MenuItemViewHolder(context, mInflater.inflate(R.layout.home_menu_container, parent, false)); case TYPE_JAVA: //JavaDemo return new JavaItemViewHolder(context, mInflater.inflate(R.layout.home_java_item, parent, false)); case TYPE_ANDRIOD_UI: //Android UI 頁面 return new AndroidUIItemViewHolder(context, mInflater.inflate(R.layout.home_java_item, parent, false)); case TYPE_RECYCLERVIEW: //RecyclerViewDemo return new RecyclerViewItemViewHolder(context, mInflater.inflate(R.layout.home_java_item, parent, false)); case TYPE_MYVIEW: //自定義View return new MyViewItemViewHolder(context, mInflater.inflate(R.layout.home_java_item, parent, false)); } return null; } /** * 這裡抽取一層,繼承BaseItemViewHolder,分別處理 * * @param viewHolder * @param position */ @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { BaseItemViewHolder baseViewHolder = (BaseItemViewHolder) viewHolder; baseViewHolder.onBindViewHolder(typeList.get(position)); } @Override public int getItemCount() { return typeList.size(); } }
getItemViewType:用於分配每個佈局的實體類資料
onCreateViewHolder:用於不同Type建立不同的ViewHolde,分別實現對應佈局資料顯示
onBindViewHolder :繫結傳遞對應資料
4.BaseItemViewHolder相當抽出一層,處理某些佈局的相似部分,這裡我的概述部分都是寫的差不多的,後面可能會更改。
/** * 主頁介面卡item處理基類 */ abstract class BaseItemViewHolder<T extends AbsBaseEntity> extends RecyclerView.ViewHolder { DecimalFormat mFormat; Context mContext; /** 標記線 */ private View mHeadLineV; /** 模組名 */ private TextView mHeadNameTv; /** 時間段 後期可能會換名稱*/ private TextView mHeadTimeTv; /**頭部view*/ private LinearLayout mHeadContainerLl; BaseItemViewHolder(Context context, View itemView) { super(itemView); this.mContext = context; mHeadLineV = itemView.findViewById(R.id.home_head_item_line); mHeadNameTv = (TextView) itemView.findViewById(R.id.tv_home_head_item_name); mHeadTimeTv = (TextView) itemView.findViewById(R.id.tv_home_head_item_time); mHeadContainerLl = (LinearLayout) itemView.findViewById(R.id.ll_head_container); mFormat = new DecimalFormat("#,###.##"); } public abstract void onBindViewHolder(T entity); void updateHeadInfos(T entity, int lineColor,String titleRes) { mHeadLineV.setBackgroundColor(mContext.getResources().getColor(lineColor)); mHeadNameTv.setText(titleRes); setHeadViewOnClickListener(entity, mHeadContainerLl); } private void setHeadViewOnClickListener(final T entity, View view) { if (view == null) { return; } view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onHeadViewClick(entity); } }); } /** * 頭部被點選 * @param entity T */ void onHeadViewClick(T entity){ } }
這裡實現了相同佈局的一些設定,updateInfos方法就是設定相同佈局值以及點選時間監聽,用於頁面跳轉。
5.Banner廣告頁的實現。
這個得說說,最早接觸的輪播是產品考慮到兩個相同類似佈局平鋪在頁面上不好看,建議坐左右滑動,這個可以做就是(viewpage+Fragment)+viewpage
然後產品看了iOS有輪播挺好,要我們也加上,兩個佈局viewpage只支援左右滑動播,而iOS的無線輪播可以一直同向劃。不好意思做不了,只能左右劃。索性那個頁面改了,沒再做要求。而後,新需求有活動頁面輪播,這下聰明瞭,不自己寫了,github上去找,可以無限單向滑動,可以單項輪播,產品又發現了,在最後兩張輪播切換的時候不是橫著過來而是替換,改!
這裡的原因呢,Android控制元件並不像iOS控制元件那樣,ViewPage滑到最後一項不能再向右滑動了。
現在github上流行的Banner
運用部落格連結:
真心羨慕那些會封裝大神的大牛。
好,再說說這邊的實現,不好意思沒有輪播,只有手動滑動。
BannerItemViewHolder
public class BannerItemViewHolder extends BaseItemViewHolder<BannerEntity> { /** banner介面卡*/ private ViewPagerInViewPager mViewPagerInViewPager; /** banner指示器*/ private LinearLayout mIndicatorLl; /** 指示器內容*/ private ImageView[] mImageViews; public BannerItemViewHolder(Context context, View itemView) { super(context, itemView); mViewPagerInViewPager = (ViewPagerInViewPager) itemView.findViewById(R.id.vp_home_banner); mIndicatorLl = (LinearLayout) itemView.findViewById(R.id.ll_home_banner_indicator); } @Override public void onBindViewHolder(BannerEntity entity) { mIndicatorLl.removeAllViews(); BannerViewPagerAdapter imgViewPagerAdapter = new BannerViewPagerAdapter(mContext, entity.getBannerItems()); mViewPagerInViewPager.setAdapter(imgViewPagerAdapter); if (entity.getBannerItems().size() == 1) { //一張圖時不做切換操作 return; } initIndicatorView(entity); mViewPagerInViewPager.clearOnPageChangeListeners(); mViewPagerInViewPager.addOnPageChangeListener(new GuidePageChangeListener(mImageViews)); } /** * <li>初始化廣告切換指引view</li> */ private void initIndicatorView(BannerEntity entity) { mImageViews = new ImageView[entity.getBannerItems().size()]; for (int i = 0, len = entity.getBannerItems().size(); i < len; i++) { ImageView imageView = new ImageView(mContext); int piexSize = mContext.getResources().getDimensionPixelSize(R.dimen.banner_indicator_size); imageView.setLayoutParams(new LinearLayout.LayoutParams(piexSize, piexSize)); int piexPadding = mContext.getResources().getDimensionPixelSize(R.dimen.guide_indicator_padding); mImageViews[i] = imageView; mImageViews[i].setBackgroundResource(R.drawable.shape_circle_indicator_press); if (i != 0) { mImageViews[i].setBackgroundResource(R.drawable.shape_circle_indicator_normal); } if (i != 0) { TextView nullView = new TextView(mContext); nullView.setLayoutParams(new LinearLayout.LayoutParams(piexPadding, piexPadding)); mIndicatorLl.addView(nullView); } mIndicatorLl.addView(mImageViews[i]); } } /** * viewpager滑動監聽 */ private class GuidePageChangeListener implements ViewPager.OnPageChangeListener { private ImageView[] mImageViews; GuidePageChangeListener(ImageView[] imageViews) { this.mImageViews = imageViews; } @Override public void onPageScrollStateChanged(int arg0) { } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageSelected(int arg0) { for (int i = 0; i < mImageViews.length; i++) { mImageViews[arg0].setBackgroundResource(R.drawable.shape_circle_indicator_press); if (arg0 != i) { mImageViews[i].setBackgroundResource(R.drawable.shape_circle_indicator_normal); } } } } }圖片滑動指示點的處理,viewPage裡裝第幾張圖,第幾張圖就變亮色
自定義容器ViewPagerInViewPager
/** * <li> ViewPager內嵌Viewpager </li> */ public class ViewPagerInViewPager extends ViewPager { public ViewPagerInViewPager(Context context) { super(context); } public ViewPagerInViewPager(Context context, AttributeSet attrs) { super(context, attrs); } PointF downPoint = new PointF(); OnSingleTouchListener onSingleTouchListener; @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent evt) { switch (evt.getAction()) { case MotionEvent.ACTION_DOWN: // 記錄按下時候的座標 downPoint.x = evt.getX(); downPoint.y = evt.getY(); if (this.getChildCount() > 1) { //有內容,多於1個時 // 通知其父控制元件,現在進行的是本控制元件的操作,不允許攔截 getParent().requestDisallowInterceptTouchEvent(true); } break; case MotionEvent.ACTION_MOVE: if (this.getChildCount() > 1) { //有內容,多於1個時 // 通知其父控制元件,現在進行的是本控制元件的操作,不允許攔截 getParent().requestDisallowInterceptTouchEvent(true); } break; case MotionEvent.ACTION_UP: // 在up時判斷是否按下和鬆手的座標為一個點 if (PointF.length(evt.getX() - downPoint.x, evt.getY() - downPoint.y) < (float) 5.0) { onSingleTouch(this); return true; } break; } return super.onTouchEvent(evt); } public void onSingleTouch(View v) { if (onSingleTouchListener != null) { onSingleTouchListener.onSingleTouch(v); } } public interface OnSingleTouchListener { public void onSingleTouch(View v); } public void setOnSingleTouchListener( OnSingleTouchListener onSingleTouchListener) { this.onSingleTouchListener = onSingleTouchListener; } }有左右滑動及點選事件和滑動衝突的處理方法
BannerViewPagerAdapter
/** * <li>廣告圖介面卡 </li> */ public class BannerViewPagerAdapter extends PagerAdapter { private Context mContext; private List<BannerEntity.BannerItem> mBannerList; private View[] mViews; public BannerViewPagerAdapter(Context context, List<BannerEntity.BannerItem> photos) { this.mContext = context; this.mBannerList = photos; initView(); } private void initView() { mViews = new View[mBannerList.size()]; LayoutInflater inflater = LayoutInflater.from(mContext); for (int index = 0, len = mBannerList.size(); index < len; index++) { mViews[index] = inflater.inflate(R.layout.home_banner_item, null, false); } } @Override public int getCount() { return mBannerList.size(); } @Override public boolean isViewFromObject(View arg0, Object arg1) { return arg0 == arg1; } @Override public int getItemPosition(Object object) { return super.getItemPosition(object); } @Override public void destroyItem(ViewGroup arg0, int arg1, Object arg2) { int position = arg1 % mBannerList.size(); arg0.removeView(mViews[position]); } @Override public Object instantiateItem(ViewGroup arg0, int arg1) { int position = arg1 % mBannerList.size(); ImageView view = (ImageView) mViews[position].findViewById(R.id.iv_active_banner_item); String url = mBannerList.get(position).getImageUrl(); if (null != url && !url.equals("")) { Picasso.with(mContext) .load(url) // .resize(view.getMeasuredWidth(),view.getMeasuredHeight()) // .centerInside() .fit() .into(view); } try { if (mViews[position].getParent() == null) arg0.addView(mViews[position], 0); else { ((ViewGroup) mViews[position].getParent()).removeAllViews(); arg0.addView(mViews[position], 0); } } catch (Exception e) { e.printStackTrace(); } setOnClickListener(mViews[position].findViewById(R.id.ll_active_banner_item), position); return mViews[position]; } @Override public void restoreState(Parcelable arg0, ClassLoader arg1) { } @Override public Parcelable saveState() { return null; } private void setOnClickListener(View view, final int index) { View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { if (!StringUtils.isEmpty(mBannerList.get(index).getWebUrl())){ WebViewActivity.startWebViewActivity(mContext, mBannerList.get(index).getWebUrl()); } } }; view.setOnClickListener(listener); } }1)Picasso載入圖片
gradle新增
compile 'com.squareup.picasso:picasso:2.5.2'
2)監聽設定新增跳轉的一些方法
banner就到這,有機會再深入研究
6.選單欄實現
MenuItemViewHolder
public class MenuItemViewHolder extends BaseItemViewHolder<MenuEntity> { private LinearLayout mContainerLl; MenuItemViewHolder(Context context, View itemView) { super(context, itemView); mContainerLl = (LinearLayout) itemView.findViewById(R.id.ll_home_container); } @Override public void onBindViewHolder(MenuEntity entity) { mContainerLl.removeAllViews(); WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); Point point = new Point(); windowManager.getDefaultDisplay().getSize(point); float itemWidth = point.x / 4.5f; for (int index = 0, len = entity.getmItemEntities().size(); index < len; index ++) { MenuEntity.MenuItemEntity itemEntity = entity.getmItemEntities().get(index); autoAddMenuItemView(entity, itemWidth, itemEntity.getImgId(), itemEntity.getTitle()); } } private void autoAddMenuItemView(final MenuEntity entity, float width, int imageResId, String textResId) { View view = LayoutInflater.from(mContext).inflate(R.layout.home_menu_item, null); ImageView imageView = (ImageView) view.findViewById(R.id.iv_home_menu); TextView textView = (TextView) view.findViewById(R.id.tv_home_menu); imageView.setImageResource(imageResId); textView.setText(textResId); view.setId(imageResId); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams(); if (params == null) { params = new LinearLayout.LayoutParams((int) width, LinearLayout.LayoutParams.WRAP_CONTENT); } params.width = (int) width; view.setLayoutParams(params); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.drawable.java_img: Intent intent = new Intent(mContext,JavaDemoActivity.class); mContext.startActivity(intent); break; case R.drawable.ui_img: Intent intent1 = new Intent(mContext,AndroidUIActivity.class); mContext.startActivity(intent1); break; case R.drawable.recyclerview_img: Intent intent2 = new Intent(mContext,RecyclerViewActivity.class); mContext.startActivity(intent2); break; case R.drawable.view_img: Intent intent3 = new Intent(mContext,MyViewActivity.class); mContext.startActivity(intent3); break; } } }); mContainerLl.addView(view); } }
ll_home_container.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="@color/home_gray_item_bg" android:orientation="horizontal" android:paddingBottom="10dp"> <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@color/white" android:scrollbars="none"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ll_home_container" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/white" android:orientation="horizontal"> </LinearLayout> </HorizontalScrollView> </LinearLayout>
看了這裡的程式碼才知道,還能這樣動態新增 View 的,尤其Point也得加強,大神寫得程式碼簡直藝術
7.概述欄
JavaItemViewHolder(這下前面的BaseItemViewHolder,就起到左右了,點選事假方法新增,相同佈局的方法新增)