近期在做博客迁移,原文地址:原文地址
今天给大家带来的是高仿蘑菇街的首页,现在这种页面的格式很流行,一般都用在首页上,能够很好的利用手机屏幕的空间,毕竟手机屏幕就这么一点点大,想要放很多东西呢,这种布局方式还是很不错的。
先看一下效果:点击打开链接
说一下思路:其实思路很简单,把所有控件都包括进一个自定义ViewGroup里,可以继承自ScrollView,也可以继承自LinearLayout,这里我选择LinearLayout,布局上减少了一层层级,性能上显得更加优秀。然后既然自定了ViewGroup,我们就需要对滑动进行事件分发,这里我们只要对y方向的滑动进行判断就可以了,横向的并不冲突。怎么dispatch呢?看一下下面的图示:当滑动距离达到view1+view2的高度之前,自定义viewGroup(在这里我取名为MoguLayout,不知道应该取什么名字比较好)对滑动事件进行拦截,滑动事件自己处理,当view1+view2都隐藏了以后,且用户是继续向上滑动的时候MoguLayout放过,不拦截,滑动事件留给ListVIew自己处理,当view1+view2隐藏,且listview的第一个item到顶部且用户向下滑动到时候,这个时候MoguLayout又要拦截了。

ok,思路分析得也差不多了,直接上代码:
第一步,就是总体框架的搭建,主要就是Fragment和ViewPager,这里先贴出Fragment的代码;
public class ListFragment extends Fragment{
private static final int LIST_NUM = 20;
private View view;
@ViewInject(R.id.id_listview)
private ListView listView;
private int type;
private List<String> stringList = new ArrayList<>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
if (bundle != null){
type = bundle.getInt("type");
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.layout_fragment,null);
ViewUtils.inject(this,view);
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null){
parent.removeView(view);
}
initData();
initListView();
return view;
}
private void initData() {
stringList.clear();
for (int i = 0;i < LIST_NUM;i++){
stringList.add("列表"+type+":"+i);
}
}
private void initListView() {
listView.setAdapter(new ArrayAdapter<String>(getActivity(),android.R.layout.simple_list_item_1,stringList));
}
}
第二步,就是先把界面都弄出来,activity_main和MainActivity代码如下:
<com.jiangjieqiang.mogulayout.view.MoguLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:id="@id/id_top_banner"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@color/lightpink">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="轮播图"
android:layout_centerInParent="true"/>
</RelativeLayout>
<!--<RelativeLayout-->
<!--android:id="@id/id_horizontalview"-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="100dp"-->
<!--android:background="@color/blue">-->
<!--</RelativeLayout>-->
<HorizontalScrollView
android:id="@id/id_horizontalview"
android:layout_width="match_parent"
android:layout_height="100dp"
android:scrollbars="none">
<LinearLayout
android:id="@+id/id_horizontalview_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
<com.jiangjieqiang.mogulayout.view.AutoHorizontalScrollView
android:id="@id/id_horizontalmenu"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/white"
android:layout_gravity="center"
android:scrollbars="none">
<LinearLayout
android:id="@+id/tab_layout"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"></LinearLayout>
</com.jiangjieqiang.mogulayout.view.AutoHorizontalScrollView>
<android.support.v4.view.ViewPager
android:background="@color/green"
android:id="@id/id_viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.jiangjieqiang.mogulayout.view.MoguLayout>
MainActivity:
public class MainActivity extends AppCompatActivity {
private static final int NUM_FRAGMENT = 10;
@ViewInject(R.id.id_viewpager)
private ViewPager viewPager;
@ViewInject(R.id.id_horizontalmenu)
private AutoHorizontalScrollView menu;
@ViewInject(R.id.tab_layout)
private LinearLayout tabLayouts;
@ViewInject(R.id.id_horizontalview_layout)
private LinearLayout typeLayouts;
private List<ListFragment> fragmentList = new ArrayList<>();
private List<String> titles = new ArrayList<>();
private List<TextView> textViews = new ArrayList<>();
private List<ItemVO> itemList = new ArrayList<>();
private String[] typeTitles = {"李易峰专区","当剩女遇见桃花","春季遮肉必看",
"甜心开胃菜","租男友","开学衣橱大改造","没有PS你可以吗","藏肉显瘦搭配"};
private int[] typeImgs = {R.mipmap.icon1,R.mipmap.icon1,R.mipmap.icon1,R.mipmap.icon1,
R.mipmap.icon1,R.mipmap.icon1,R.mipmap.icon1,R.mipmap.icon1};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
initFragments();
initView();
initTypeLayout();
}
private void initFragments() {
for (int i = 0;i < NUM_FRAGMENT;i++){
titles.add("title"+i);
ListFragment fragment = new ListFragment();
Bundle bundle = new Bundle();
bundle.putInt("type",i);
fragment.setArguments(bundle);
fragmentList.add(fragment);
LinearLayout tabLayout = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.menu_item,null);
final TextView textView = (TextView)tabLayout.findViewById(R.id.tab_tv);
textView.setText(titles.get(i));
final int id = i;
tabLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
setSelector(id);
}
});
tabLayouts.addView(tabLayout);
textViews.add(textView);
}
}
private void initView() {
setSelector(0);
viewPager.setCurrentItem(0);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
setSelector(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public int getCount() {
return NUM_FRAGMENT;
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
});
}
/**
* 选中效果
* @param position
*/
private void setSelector(final int position) {
for (int i = 0;i < NUM_FRAGMENT; i++){
if (position == i){
viewPager.setCurrentItem(position);
menu.resetScrollWidth(position);
textViews.get(i).setBackgroundResource(R.mipmap.bg_nav_contacts);
}else {
textViews.get(i).setBackgroundResource(R.color.alpha);
}
}
}
/**
* 初始化横向滑动的layouts
*/
private void initTypeLayout() {
initItemList();
for (final ItemVO itemVO : itemList){
FrameLayout tabLayout = (FrameLayout)LayoutInflater.from(this).inflate(R.layout.horizontal_item,null);
ImageView imageView = (ImageView)tabLayout.findViewById(R.id.id_horizontal_item_img);
TextView textView = (TextView)tabLayout.findViewById(R.id.id_horizontal_item_desc);
imageView.setImageResource(itemVO.getImage());
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
textView.setText(itemVO.getDesc());
tabLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//intent进入其他页面
//……
Toast.makeText(MainActivity.this, "进入页面", Toast.LENGTH_SHORT).show();
}
});
LinearLayout.LayoutParams vlp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
vlp.setMargins(calculateDpToPx(5),calculateDpToPx(5),calculateDpToPx(5),calculateDpToPx(5));
typeLayouts.addView(tabLayout,vlp);
}
}
private void initItemList() {
itemList.clear();
for (int i = 0;i < typeImgs.length;i ++){
ItemVO itemVO = new ItemVO(typeTitles[i],typeImgs[i]);
itemList.add(itemVO);
}
}
private int calculateDpToPx(int padding_in_dp){
final float scale = getResources().getDisplayMetrics().density;
return (int) (padding_in_dp * scale + 0.5f);
}
}
其中我对listview的导航进行了自定义view的处理,其实我就是在其中公开了一个方法,给MainActivity里的viewPager当滑动到页面的时候调用:
AutoHorizontalScrollView:
public class AutoHorizontalScrollView extends HorizontalScrollView{
public AutoHorizontalScrollView(Context context) {
super(context);
}
public AutoHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AutoHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 当item过多时,主屏幕显示不了,就重置item的width的位置,并让下一个item显示在屏幕的一半
* @param index
*/
public void resetScrollWidth(int index) {
ViewGroup parent = (ViewGroup) getChildAt(0);
if (index < 0 || index >= parent.getChildCount()) {
return;
}
View view;
int left = 0;
for (int i = 0; i < index; i++) {
view = parent.getChildAt(i);
view.measure(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
left += view.getMeasuredWidth();
}
view = parent.getChildAt(index);
view.measure(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
int right = left + view.getMeasuredWidth();
if (right < getWidth()/ 2) {
this.smoothScrollTo(0, 0);
}
else {
this.smoothScrollTo(right - (getWidth()/ 2), 0);
}
}
}
3、重头戏,也就是重点MoguLayout的实现;
首先,就是对一些我们要使用到的变量进行定义以及初始化,然后呢我们有viewpager,viewpager是不会自己去测量里面孩子的高度的,我们需要在onMeasure()方法里给它设置一个高度。
public class MoguLayout extends LinearLayout{
private View topView;
private View horizontalScrollView;
private AutoHorizontalScrollView menu;
private ViewPager viewPager;
private ListView listView;
private OverScroller scroller;
private VelocityTracker mVelocityTracker;
private int mTouchSlop;
private int mMaximumVelocity, mMinimumVelocity;
private int distanceFromViewPagerToX;
private float mLastY;
private boolean mDragging;
private boolean isInControl = false;
private boolean isTopHidden = false;
public MoguLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(LinearLayout.VERTICAL);
scroller = new OverScroller(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mMaximumVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
mMinimumVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
topView = findViewById(R.id.id_top_banner);
horizontalScrollView = findViewById(R.id.id_horizontalview);
viewPager = (ViewPager)findViewById(R.id.id_viewpager);
menu = (AutoHorizontalScrollView)findViewById(R.id.id_horizontalmenu);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ViewGroup.LayoutParams params = viewPager.getLayoutParams();
params.height = getMeasuredHeight() - menu.getMeasuredHeight();
}
然后,我们需要随时获取view1+view2在屏幕上显示的高度,这个高度我们要用来进行对滑动事件是否拦截的判断。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
distanceFromViewPagerToX = topView.getMeasuredHeight()+horizontalScrollView.getMeasuredHeight();
}
之后就是三部曲了:1、自定了viewGroup,滑动就要自己写,2、dispatchTouchEvent()进行分发,3、onInterceptTouchEvent()进行拦截判断,具体的逻辑分析之前已经分析过了,那就上代码咯:
@Override
public boolean onTouchEvent(MotionEvent event) {
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(event);
int action = event.getAction();
float y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (!scroller.isFinished())
scroller.abortAnimation();
mLastY = y;
return true;
case MotionEvent.ACTION_MOVE:
float dy = y - mLastY;
//判断是滑动还是点击
if (!mDragging && Math.abs(dy) > mTouchSlop) {
mDragging = true;
}
if (mDragging) {
scrollBy(0, (int) -dy);
// 如果topView隐藏,且上滑动时,则改变当前事件为ACTION_DOWN
if (getScrollY() == distanceFromViewPagerToX && dy < 0) {
event.setAction(MotionEvent.ACTION_DOWN);
dispatchTouchEvent(event);
isInControl = false;
}
}
mLastY = y;
break;
case MotionEvent.ACTION_CANCEL:
mDragging = false;
recycleVelocityTracker();
if (!scroller.isFinished()) {
scroller.abortAnimation();
}
break;
case MotionEvent.ACTION_UP:
mDragging = false;
//初始化
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocityY = (int) mVelocityTracker.getYVelocity();
if (Math.abs(velocityY) > mMinimumVelocity) {
fling(-velocityY);
}
recycleVelocityTracker();
break;
}
return super.onTouchEvent(event);
}
/**
* 当滑动速度比较大的时候,实现快速滑动
* @param velocityY
*/
public void fling(int velocityY) {
//
scroller.fling(0, getScrollY(), 0, velocityY, 0, 0, 0, distanceFromViewPagerToX);
invalidate();
}
@Override
public void scrollTo(int x, int y) {
if (y < 0) {
y = 0;
}
if (y > distanceFromViewPagerToX) {
y = distanceFromViewPagerToX;
}
if (y != getScrollY()) {
super.scrollTo(x, y);
}
isTopHidden = getScrollY() == distanceFromViewPagerToX;
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(0, scroller.getCurrY());
invalidate();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
float y = ev.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
float dy = y - mLastY;
getCurrentListView();
View view = listView.getChildAt(listView.getFirstVisiblePosition());
if (!isInControl && view != null && view.getTop() == 0 && isTopHidden && dy > 0) {
isInControl = true;
ev.setAction(MotionEvent.ACTION_CANCEL);
MotionEvent ev2 = MotionEvent.obtain(ev);
dispatchTouchEvent(ev);
ev2.setAction(MotionEvent.ACTION_DOWN);
return dispatchTouchEvent(ev2);
}
break;
}
return super.dispatchTouchEvent(ev);
}
private void getCurrentListView() {
int currentItem = viewPager.getCurrentItem();
PagerAdapter a = viewPager.getAdapter();
FragmentPagerAdapter fadapter = (FragmentPagerAdapter) a;
Fragment item = (Fragment) fadapter.instantiateItem(viewPager,
currentItem);
listView = (ListView) (item.getView().findViewById(R.id.id_listview));
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
float y = ev.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
float dy = y - mLastY;
getCurrentListView();
if (Math.abs(dy) > mTouchSlop) {
//滑动
mDragging = true;
View view = listView.getChildAt(listView.getFirstVisiblePosition());
// 拦截条件:topView没有隐藏
// 或listView在顶部 && topView隐藏 && 下拉
if (!isTopHidden || (view != null && view.getTop() == 0 && isTopHidden && dy > 0)) {
initVelocityTrackerIfNotExists();
mLastY = y;
mVelocityTracker.addMovement(ev);
return true;
}
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mDragging = false;
recycleVelocityTracker();
break;
}
return super.onInterceptTouchEvent(ev);
}
private void initVelocityTrackerIfNotExists() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
}
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
这里我们对快速滑动进行了特殊处理,当滑动速度大于minVelocityTracker的时候,我们处理的圆润一点,直接滑动到view1+view显示或者隐藏两种状态。ok,基本就大功告成了!
项目地址:点击打开链接

网友评论