1. 程式人生 > >實現蘑菇街首頁效果

實現蘑菇街首頁效果

打算出一個系列,專治現在市面上各種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,基本就大功告成了!