从这节开始,主要是对这个商城项目的主页面做设计和开发,根据淘宝的页面结构做参考,设计一款商城购物APP。
先看一下淘宝的首页。
整体的布局就是这样的,所以先从页面框架开始搭起来。
1、页面整体布局
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="30dp"
app:navigationIcon="@drawable/ic_scan">
<androidx.appcompat.widget.SearchView
android:id="@+id/sv_main"
android:layout_width="200dp"
android:layout_height="30dp"
app:queryHint="输入要查询的商品"
app:iconifiedByDefault="false"
android:background="@drawable/search_bg"></androidx.appcompat.widget.SearchView>
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/toolbar"
app:tabIndicatorHeight="3dp"
app:tabIndicatorGravity="bottom"
app:tabIndicatorColor="@color/colorRed"
app:tabMode="scrollable"></com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/vp2_tab"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/tab_main"></androidx.viewpager2.widget.ViewPager2>
</RelativeLayout>
整体的布局还是使用TabLayout + ViewPager2实现,关于这两个控件的具体使用,在之前的项目中已经说过很多了,尤其是ViewPager2,跟之前的ViewPager还是有很多不同的地方。
经过网络请求之后,得到每个Tab的标题,以及每个标题的id,在后续的页面实现中要用到,最终通过ViewPager2和TabLayout的联动之后:
//获取的Tab数据集合
data = tabBean.getData();
Log.e("TAG","data=="+tabBean.getMessage());
//ViewPager2中嵌套Fragment
vp2_tab.setAdapter(new FragmentStateAdapter(this) {
@NonNull
@Override
public Fragment createFragment(int position) {
return fragmentList.get(position);
}
@Override
public int getItemCount() {
return fragmentList.size();
}
});
TabLayoutMediator mediator = new TabLayoutMediator(tab_main, vp2_tab, new TabLayoutMediator.TabConfigurationStrategy() {
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
tab.setText(data.get(position).getTitle());
}
});
mediator.attach();
其中注意一点:如果使用的是ViewPager
,那么需要在ViewPager
设置适配器的时候,从写setPageTitle()
方法,不然,TabLayout
不会显示标题,但是在使用ViewPager2
的时候,就不需要了,不会出现这个状况。
看一下效果:
2、Fragment与Fragment之间的数据传递
在TabLayout的第一个页面是推荐页面,推荐页面的数据需要根据该Tab标题的对应id获取,这就涉及到数据的传递,在首页是一个Fragment页面,推荐页面也是一个Fragment页面,在Fragment之间的数据传递,在之前的章节中有涉及到,可以去《Android模块开发-----用户信息模块(Fragment和Activity之间数据的传递)》,这里使用setArguments
方法进行数据传递。
tab_main.addOnTabSelectedListener(new MyOnTabSelectionListener());
}
private class MyOnTabSelectionListener implements TabLayout.OnTabSelectedListener {
@Override
public void onTabSelected(TabLayout.Tab tab) {
Log.e("TAG","position=="+tab.getPosition());
int position = tab.getPosition();
// Log.e("TAG","fragment==="+fragmentList.get(tab.getPosition()));
Bundle bundle = new Bundle();
bundle.putInt("id",data.get(position).getId());
fragmentList.get(position).setArguments(bundle);
}
当点击Tab时,会切换Fragment
页面,所以需要获取当前Tab的位置,将数据保存到Bundle
中,获取集合中该位置的Fragment
,调用setArguments(Bundle bundle)
发送数据。
在对应的Fragment
页面,调用getArguments
得到对应的Bundle
数据,获取id值;但也存在特殊情况,在之前玩Android
项目公众号模块中,因为TabLayout
是默认选中第一个Tab,上述设置方式在第一个Tab不起作用,因此需要得到默认的选中位置,发送数据。
int curPosition = tab_main.getSelectedTabPosition();
Log.e("TAG","selectedTabPosition==="+curPosition);
int id = data.get(curPosition).getId();
Bundle bundle = new Bundle();
bundle.putInt("id",id);
fragmentList.get(curPosition).setArguments(bundle);
3、推荐界面
因为数据源比较多,因此抽取出来几个作为轮播图在首页显示,在之前的章节中介绍过使用ViewPager自定义轮播图,因此在这里就不多说,直接上核心代码了。
推荐界面使用RecyclerView,RecyclerView实现多种布局方式,因此轮播图作为其中一种:
if(holder instanceof BannerViewHolder){
//banner adapter
for (int i = 0; i < 5; i++) {
ImageView imageView = new ImageView(context);
String pict_url = data.get(i).getPict_url();
Glide.with(context).load("http:"+pict_url).centerCrop()
.into(imageView);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
150);
imageView.setLayoutParams(params);
imageViewList.add(imageView);
}
bannerAdapter = new BannerAdapter2(context,imageViewList);
((BannerViewHolder) holder).vp_banner.setAdapter(bannerAdapter);
Log.e("TAG","position == "+position);
}
效果:
实现之后,因为在外层有一个ViewPager与定义轮播图的ViewPager产生了滑动冲突,导致轮播图在滑动时,事件(DOWN事件)被外层ViewPager消费了,导致在轮播图处的点击事件无法响应,因此需要处理滑动冲突。
滑动冲突的处理,分为两种:一种是外部处理,通常是在父容器处理,判断是否对事件拦截,在onInterceptTouchEvent
方法中处理;另一种是内部处理,在子容器中判断父容器能否拦截该事件,在dispatchTouchEvent
方法中处理。滑动冲突设计到事件分发机制,之后拿出时间再写一篇关于事件分发的博客,这里就把事件冲突的处理先贴出来吧,逻辑也挺简单的。
public class BannerView extends ViewPager {
public BannerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
private float x1;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
x1 = ev.getX();
//不允许父类消费事件
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
//得到当前的位置下标
int currentItem = getCurrentItem();
float x2 = ev.getX();
//如果当前是第一页,还是向左滑动(手指的方向)-----滑动到第二页,那么父类不能拦截
if(currentItem == 0){
if((x1-x2)>10){
getParent().requestDisallowInterceptTouchEvent(true);
}else{
//第一页向右滑动,那就交给父类处理
getParent().requestDisallowInterceptTouchEvent(false);
}
}else if(currentItem == (getAdapter().getCount() - 1)){
//如果是最后一页,还是向左滑动--交给父容器处理事件
if((x1-x2) > 10){
getParent().requestDisallowInterceptTouchEvent(false);
}else{
getParent().requestDisallowInterceptTouchEvent(true);
}
}else{
//其他页面,都是给ViewPager消费
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
}
这样使用自定义的View代替ViewPager,因为本来就继承了ViewPager,只是重写了ViewPager的事件分发机制,这样滑动冲突就解决了。