ViewDragHelper實戰 自己打造Drawerlayout

jopen 8年前發布 | 12K 次閱讀 Android開發 移動開發

轉載請標明出處:
http://blog.csdn.net/lmj623565791/article/details/47396187
本文出自:【張鴻洋的博客】

一、概述

中間拖了蠻長時間了,在上一篇我們介紹了ViewDragHelper,詳情:ViewDragHelper完全解析,當然了,上一篇都是小示例的形式去演示代碼功能,并不能給人一種實用的感覺。那么,本篇博客就準備實用ViewDragHelper來實現一個DrawerLayout的效果,當然了,大家也可以選擇直接去看Drawerlayout的源碼。相信側滑大家肯定不陌生,網絡上流傳無數個版本,其實利用ViewDragHelper就能方便的寫出比較不錯的側滑代碼~

那么首先上個效果圖:

ok,其實和DrawerLayout效果類似,當然不是完全的一致~~本篇的主要目的也是演示VDH的實戰用法,大家在選擇側滑的時候還是建議使用DrawerLayout.

二、實戰

(一)布局文件

首先看一下布局文件:

<com.zhy.learn.view.LeftDrawerLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"                                   android:layout_width="match_parent"
android:id="@+id/id_drawerlayout"                                     android:layout_height="match_parent"
    >

    <!--content-->
    <RelativeLayout
        android:clickable="true"
        android:layout_width="match_parent"
        android:background="#44ff0000"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/id_content_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="hello world"
            android:textSize="40sp"
            android:layout_centerInParent="true"/>
    </RelativeLayout>

    <!--menu-->
    <FrameLayout
        android:id="@+id/id_container_menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    </FrameLayout>


</com.zhy.learn.view.LeftDrawerLayout>

ok,可以看到我們的LeftDrawerLayout里面一個是content,一個是menu~為了方便,我們就默認child0為content,child1為menu。

(二)LeftDrawerLayout

package com.zhy.learn.view;

import android.content.Context;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import com.zhy.util.common.L;

/** * Created by zhy on 15/5/29. */
public class LeftDrawerLayout extends ViewGroup {
    private static final int MIN_DRAWER_MARGIN = 64; // dp
    /** * Minimum velocity that will be detected as a fling */
    private static final int MIN_FLING_VELOCITY = 400; // dips per second

    /** * drawer離父容器右邊的最小外邊距 */
    private int mMinDrawerMargin;

    private View mLeftMenuView;
    private View mContentView;

    private ViewDragHelper mHelper;
    /** * drawer顯示出來的占自身的百分比 */
    private float mLeftMenuOnScrren;


    public LeftDrawerLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        //setup drawer's minMargin
        final float density = getResources().getDisplayMetrics().density;
        final float minVel = MIN_FLING_VELOCITY * density;
        mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);

        mHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback()
        {
            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx)
            {
                int newLeft = Math.max(-child.getWidth(), Math.min(left, 0));
                return newLeft;
            }

            @Override
            public boolean tryCaptureView(View child, int pointerId)
            {
                L.e("tryCaptureView");
                return child == mLeftMenuView;
            }

            @Override
            public void onEdgeDragStarted(int edgeFlags, int pointerId)
            {
                L.e("onEdgeDragStarted");
                mHelper.captureChildView(mLeftMenuView, pointerId);
            }

            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel)
            {
                L.e("onViewReleased");
                final int childWidth = releasedChild.getWidth();
                float offset = (childWidth + releasedChild.getLeft()) * 1.0f / childWidth;
                mHelper.settleCapturedViewAt(xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth, releasedChild.getTop());
                invalidate();
            }

            @Override
            public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)
            {
                final int childWidth = changedView.getWidth();
                float offset = (float) (childWidth + left) / childWidth;
                mLeftMenuOnScrren = offset;
                //offset can callback here 
                changedView.setVisibility(offset == 0 ? View.INVISIBLE : View.VISIBLE);
                invalidate();
            }

            @Override
            public int getViewHorizontalDragRange(View child)
            {
                return mLeftMenuView == child ? child.getWidth() : 0;
            }
        });
        //設置edge_left track
        mHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
        //設置minVelocity
        mHelper.setMinVelocity(minVel);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(widthSize, heightSize);

        View leftMenuView = getChildAt(1);
        MarginLayoutParams lp = (MarginLayoutParams)
                leftMenuView.getLayoutParams();

        final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
                mMinDrawerMargin + lp.leftMargin + lp.rightMargin,
                lp.width);
        final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
                lp.topMargin + lp.bottomMargin,
                lp.height);
        leftMenuView.measure(drawerWidthSpec, drawerHeightSpec);


        View contentView = getChildAt(0);
        lp = (MarginLayoutParams) contentView.getLayoutParams();
        final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
                widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
        final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
                heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
        contentView.measure(contentWidthSpec, contentHeightSpec);

        mLeftMenuView = leftMenuView;
        mContentView = contentView;


    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        View menuView = mLeftMenuView;
        View contentView = mContentView;

        MarginLayoutParams lp = (MarginLayoutParams) contentView.getLayoutParams();
        contentView.layout(lp.leftMargin, lp.topMargin,
                lp.leftMargin + contentView.getMeasuredWidth(),
                lp.topMargin + contentView.getMeasuredHeight());

        lp = (MarginLayoutParams) menuView.getLayoutParams();

        final int menuWidth = menuView.getMeasuredWidth();
        int childLeft = -menuWidth + (int) (menuWidth * mLeftMenuOnScrren);
        menuView.layout(childLeft, lp.topMargin, childLeft + menuWidth,
                lp.topMargin + menuView.getMeasuredHeight());
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        boolean shouldInterceptTouchEvent = mHelper.shouldInterceptTouchEvent(ev);
        return shouldInterceptTouchEvent;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        mHelper.processTouchEvent(event);
        return true;
    }


    @Override
    public void computeScroll()
    {
        if (mHelper.continueSettling(true))
        {
            invalidate();
        }
    }

    public void closeDrawer()
    {
        View menuView = mLeftMenuView;
        mLeftMenuOnScrren = 0.f;
        mHelper.smoothSlideViewTo(menuView, -menuView.getWidth(), menuView.getTop());
    }

    public void openDrawer()
    {
        View menuView = mLeftMenuView;
        mLeftMenuOnScrren = 1.0f;
        mHelper.smoothSlideViewTo(menuView, 0, menuView.getTop());
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams()
    {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    public LayoutParams generateLayoutParams(AttributeSet attrs)
    {
        return new MarginLayoutParams(getContext(), attrs);
    }

    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)
    {
        return new MarginLayoutParams(p);
    }

}

哈,很少的代碼就完成了我們的側滑的編寫,目測如果使用橫向的LinearLayout代碼更短。構造方法中,主要就是初始化我們的mDragHelper了,接下來onMeasure和onLayout都比較簡單,onLayout也只是去將我們的menu設置到屏幕的左側以至于不可見。onInterceptTouchEventonTouchEvent中都只需要簡單的調用mDragHelper的方法。那么核心代碼都在我們的ViewDragHelper.Callback的實例中了。注意一點:我們LeftDrawerLayout默認的LayoutParams為MarginLayoutParams,注意復寫相關方法。

接下來就把注意力放到我們的Callback中,我盡可能的按照調用的邏輯來解釋各個方法:

  • onEdgeDragStarted 如果你仔細的看過上篇,那么一定知道這個方法的回調位置;因為我們的View不可見,所以我們沒有辦法直接通過觸摸到它來把menu設置為captureView,所以我們只能設置邊界檢測,當MOVE時回調onEdgeDragStarted時,我們直接通過captureChildView手動捕獲。

  • tryCaptureView 那么既然我們手動捕獲了,為什么還需要這個方法呢?主要是因為當Drawer拉出的時候,我們通過拖拽Drawer也能進行移動菜單。

  • clampViewPositionHorizontal 主要是移動的時候去控制控制范圍,可以看到我們的int newLeft = Math.max(-child.getWidth(), Math.min(left, 0));一定>=-child.getWidth() && <=0 。

  • getViewHorizontalDragRange 為什么要復寫,我們上篇已經描述過,返回captureView的移動范圍。

  • onViewReleased 則是釋放的時候觸發的,我們計算當前顯示的百分比,以及加速度來決定是否顯示drawer,從代碼可以看出,當xvel > 0 || xvel == 0 && offset > 0.5f顯示我們的菜單,其他情況隱藏。這里注意一點xvel的值只有大于我們設置的minVelocity才會出現大于0,如果小于我們設置的值則一直是0。

  • onViewPositionChanged 整個pos變化的過程中,我們計算offset保存,這里可以使用接口將offset回調出去,方便做動畫。

ok,我們重寫的所有方法描述完成了~那么博文主要內容也就結束了~哈~是不是 so easy ~!

呃,忘了MainActivity和Fragment的代碼了~~按順序貼

(三)LeftDrawerLayoutActivity

package com.imooc.testandroid;

import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

import com.zhy.learn.view.LeftDrawerLayout;


public class LeftDrawerLayoutActivity extends ActionBarActivity {

    private LeftMenuFragment mMenuFragment;
    private LeftDrawerLayout mLeftDrawerLayout ;
    private TextView mContentTv ;


    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_left_drawer_layout);

        mLeftDrawerLayout = (LeftDrawerLayout) findViewById(R.id.id_drawerlayout);
        mContentTv = (TextView) findViewById(R.id.id_content_tv);

        FragmentManager fm = getSupportFragmentManager();
        mMenuFragment = (LeftMenuFragment) fm.findFragmentById(R.id.id_container_menu);
        if (mMenuFragment == null)
        {
            fm.beginTransaction().add(R.id.id_container_menu, mMenuFragment = new LeftMenuFragment()).commit();
        }

        mMenuFragment.setOnMenuItemSelectedListener(new LeftMenuFragment.OnMenuItemSelectedListener()
        {
            @Override
            public void menuItemSelected(String title)
            {
                mLeftDrawerLayout.closeDrawer();
                mContentTv.setText(title);
            }
        });

    }

可以看到drawer使用了一個Fragment ~~

(四) LeftMenuFragment

 import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;

/** * Created by zhy on 15/4/26. */
public class LeftMenuFragment extends ListFragment {

    private static final int SIZE_MENU_ITEM = 3;

    private MenuItem[] mItems = new MenuItem[SIZE_MENU_ITEM];

    private LeftMenuAdapter mAdapter;


    private LayoutInflater mInflater;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mInflater = LayoutInflater.from(getActivity());

        MenuItem menuItem = null;
        for (int i = 0; i < SIZE_MENU_ITEM; i++) {
            menuItem = new MenuItem(getResources().getStringArray(R.array.array_left_menu)[i], false, R.drawable.music_36px, R.drawable.music_36px_light);
            mItems[i] = menuItem;
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        view.setBackgroundColor(0xffffffff);
        setListAdapter(mAdapter = new LeftMenuAdapter(getActivity(), mItems));

    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);

        if (mMenuItemSelectedListener != null) {
            mMenuItemSelectedListener.menuItemSelected(((MenuItem) getListAdapter().getItem(position)).text);
        }
        mAdapter.setSelected(position);
    }


    //選擇回調的接口
    public interface OnMenuItemSelectedListener {
        void menuItemSelected(String title);
    }
    private OnMenuItemSelectedListener mMenuItemSelectedListener;

    public void setOnMenuItemSelectedListener(OnMenuItemSelectedListener menuItemSelectedListener) {
        this.mMenuItemSelectedListener = menuItemSelectedListener;
    }
}



public class MenuItem {

    public MenuItem(String text, boolean isSelected, int icon, int iconSelected) {
        this.text = text;
        this.isSelected = isSelected;
        this.icon = icon;
        this.iconSelected = iconSelected;
    }

    boolean isSelected;
    String text;
    int icon;
    int iconSelected;
}



/** * Created by zhy on 15/4/26. */
public class LeftMenuAdapter extends ArrayAdapter<MenuItem> {


    private LayoutInflater mInflater;

    private int mSelected;


    public LeftMenuAdapter(Context context, MenuItem[] objects) {
        super(context, -1, objects);

        mInflater = LayoutInflater.from(context);

    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {


        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.item_left_menu, parent, false);
        }

        ImageView iv = (ImageView) convertView.findViewById(R.id.id_item_icon);
        TextView title = (TextView) convertView.findViewById(R.id.id_item_title);
        title.setText(getItem(position).text);
        iv.setImageResource(getItem(position).icon);
        convertView.setBackgroundColor(Color.TRANSPARENT);

        if (position == mSelected) {
            iv.setImageResource(getItem(position).iconSelected);
            convertView.setBackgroundColor(getContext().getResources().getColor(R.color.state_menu_item_selected));
        }

        return convertView;
    }

    public void setSelected(int position) {
        this.mSelected = position;
        notifyDataSetChanged();
    }


}

貼了一大串,主要就是Fragment的代碼,內部有個ListView,所以還有個Adapter,這個Fragment在之前的博文中出現過,搬過來而已。item的布局文件就是一個TextView和ImageView~~不貼了~~大家自己下載源碼~

源碼點擊下載(導入方式注意看下ReadMe)

ok,到此結束~~hava a nice day ~~


歡迎關注我的微博:
http://weibo.com/u/3165018720


群號:463081660,歡迎入群

微信公眾號:hongyangAndroid
(歡迎關注,第一時間推送博文信息)

來自: http://blog.csdn.net//lmj623565791/article/details/47396187

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!