實現蘑菇街首頁效果
打算出一個系列,專治現在市面上各種app的各種滑動不服系列,解決各種滑動衝突問題,現在已經發現了9種樣式,打算一個一個一一破解,這是第一篇。
今天給大家帶來的是高仿蘑菇街的首頁,現在這種頁面的格式很流行,一般都用在首頁上,能夠很好的利用手機螢幕的空間,畢竟手機螢幕就這麼一點點大,想要放很多東西呢,這種佈局方式還是很不錯的。
說一下思路:其實思路很簡單,把所有控制元件都包括進一個自定義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,基本就大功告成了!