Android(安卓)UI 之 RecyclerView实现常见首页布局
本文出自:http://blog.csdn.net/dt235201314/article/details/72833218
欢迎Star(updating):https://github.com/JinBoy23520/CoderToDeveloperByTCLer
一丶效果演示
实现功能:
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基础知识,鸿神博客:
Android RecyclerView 使用完全解析 体验艺术般的控件
相应慕课网视频教程:http://www.imooc.com/learn/424
2.RecyclerView实现多布局相关知识
慕课网视频教程:不一样的RecyclerView优雅实现复杂列表布局(http://www.imooc.com/learn/731)
觉得视频版学的慢想要源码,博客版也为你找好
不一样的RecyclerView优雅实现复杂列表布局(一)
不一样的RecyclerView优雅实现复杂列表布局(二)
以上准备工作做好了,相信你已不再觉得用RecyclerView实现首页布局是什么难度了,剩下的就是找控件实现
四丶正文详解
1.先说首页布局HomeFragment的home_fragment.xml
<?xml version="1.0" encoding="utf-8"?>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:layout_below="@+id/title_home" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> layout="@layout/home_content_main" /> 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="首页"/>
这个CoordinatorLayout Android UI 部分在做详解
看home_comtent_main.xml
<?xml version="1.0" encoding="utf-8"?>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:id="@+id/lv_home_listview" android:layout_width="match_parent" android:layout_height="match_parent" />这个布局在开发中经常用到,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);}@Overridepublic 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
/** * * author : JinBiao * CSDN : http://my.csdn.net/DT235201314 * time : 2017/05/31 * desc : RecyclerView实现首页adapter * version: 1.0 **/public class HomeAdapter extends RecyclerView.Adapter
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
https://github.com/saiwu-bigkoo/Android-ConvenientBanner
运用博客链接:
Android中ConvenientBanner的使用
真心羡慕那些会封装大神的大牛。
好,再说说这边的实现,不好意思没有轮播,只有手动滑动。
BannerItemViewHolder
public class BannerItemViewHolder extends BaseItemViewHolder图片滑动指示点的处理,viewPage里装第几张图,第几张图就变亮色{ /** 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)); } /** * 初始化广告切换指引view */ 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); } } } } }
自定义容器ViewPagerInViewPager
/** *有左右滑动及点击事件和滑动冲突的处理方法ViewPager内嵌Viewpager */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
/** * 广告图适配器 */public class BannerViewPagerAdapter extends PagerAdapter { private Context mContext; private List1)Picasso加载图片mBannerList; private View[] mViews; public BannerViewPagerAdapter(Context context, List 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); }}
gradle添加
compile 'com.squareup.picasso:picasso:2.5.2'
2)监听设置添加跳转的一些方法 banner就到这,有机会再深入研究
6.菜单栏实现
MenuItemViewHolder
public class MenuItemViewHolder extends BaseItemViewHolder{ 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"?>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"> 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"> 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">
看了这里的代码才知道,还能这样动态添加 View 的,尤其Point也得加强,大神写得代码简直艺术
7.概述栏
JavaItemViewHolder(这下前面的BaseItemViewHolder,就起到左右了,点击事假方法添加,相同布局的方法添加)
/** * * author : JinBiao * CSDN : http://my.csdn.net/DT235201314 * time : 2017/06/05 * desc : Java概述页ViewHolder * version: 1.0 **/public class JavaItemViewHolder extends BaseItemViewHolder
五丶总结
1.首页实现的分享就写到这里,这只是开始,后面关于Android UI,RecyclerView,自定义View等的Demo都会依依整理,
感兴趣可以关注一下博主哦。
2.学习进度有点慢,拖了接近一个星期,要做的事情太多,反而没做好一件事,在看代码学习的过程中想着,不能只局限于应用,要深入源码,于是想着要先学自定义View,然后突然又杀出个Kotlin,上次比特币时间是不是也该学习一下区块链。保持学习是必须得,但计划更重要,进度延迟了一周,后面得跟上。保持有计划的学习。
3.RecyclerView实现首页,很多时候会出现RecyclerView + RecyclerView这里还没体现,后面可以尝试。
4.主要核心代码源自同事大神,简直欣赏艺术。多多向他们学习请教
更多相关文章
- 可用手势切换播放节目的android视频播放器
- android布局之线性布局(LinearLayout)
- Android:为什么声明控件和控件赋值要分开?
- Android(安卓)程式开发:(十)基本控件 —— 10.2 Button,ImageButton
- Android控件之AutoCompleteTextView(自动匹配输入的内容)
- Android(安卓)Toolbar控件
- UI框架之SmartTabLayout使用
- Android(安卓)Support Library-FloatingActionButton
- Android控件复习之TextView与EditText