自定义控件之DragLayout仿QQ界面

1.布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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">

    <com.itheima.draglayouthm91.DragLayout
        android:background="@drawable/bg"
        android:id="@+id/dl"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <include layout="@layout/menu" />
        <include layout="@layout/main" />
    </com.itheima.draglayouthm91.DragLayout>
</RelativeLayout>
2.菜单布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/head"/>

    <ListView
        android:id="@+id/lv_menu_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>

</LinearLayout>


3.main布局文件

<?xml version="1.0" encoding="utf-8"?>
<com.itheima.draglayouthm91.MainNoTouchLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:background="@android:color/white"
              android:orientation="vertical"
    android:id="@+id/main_no_touch_layout">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#18B4ED"
        android:orientation="vertical"
        android:padding="10dp"
        >

        <ImageView
            android:id="@+id/iv_head"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@drawable/head"/>
    </LinearLayout>
    <ListView
        android:id="@+id/lv_main_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>

</com.itheima.draglayouthm91.MainNoTouchLayout>


4.由于在菜单打开时 main界面时不可以点击的 所以要把main布局的父布局 重写拦截事件和触摸事件 

public class MainNoTouchLayout extends LinearLayout{
	public MainNoTouchLayout(Context context) {
		this(context,null);
	}

	public MainNoTouchLayout(Context context, AttributeSet attrs) {
		this(context, attrs,0);
	}

	public MainNoTouchLayout(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		init();
	}

	private void init() {

	}

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

	//判断DragLayout的状态
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
//		if(listener!=null){
//			//判断在DragLayout没有关闭的时候拦截事件
//			if(!listener.onMenuClose()){
//				return true;
//			}
//		}
//		return super.onInterceptTouchEvent(ev);
		return listener!=null&&!listener.onMenuClose();
	}

	private OnMainNoTouchListener listener;
	public interface OnMainNoTouchListener{
		boolean onMenuClose();
	}

	public void setOnMainNoTouchListener(OnMainNoTouchListener listener){
		this.listener=listener;
	}
}

5.自定义一个类继承FrameLayout

public class DragLayout extends FrameLayout {

	private ViewDragHelper helper;
	private View menu;
	private View main;
	private int maxDragRange;

	public DragLayout(Context context) {
		this(context, null);
	}

	public DragLayout(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		init();
	}

	private void init() {
		//Google推出辅助开发者处理触摸事件的类,处理界面的滚动
		//参数1:helper为哪一个视图处理触摸事件
		//参数2:处理触摸事件后的回调
		helper = ViewDragHelper.create(this, callback);
	}

	//helper进行触摸处理的回调
	private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
		//捕获视图,被捕获的视图可以进行触摸事件的处理
		//参数1:被捕获的孩子视图
		//参数2:pointerId,多指触摸的手指id
		@Override
		public boolean tryCaptureView(View child, int pointerId) {
			return child == main || child == menu;
		}

		//水平滑动的处理
		//参数1:被捕获的孩子视图
		//参数2:oldLeft+dx=targetLeft,新的left值
		//参数3:dx,系统每隔一段时间检测手指的移动的距离
		@Override
		public int clampViewPositionHorizontal(View child, int left, int dx) {
			//判断如果捕获的是main,处理main滑动的范围,不能小于0,不能大于最大范围
			if (child == main) {
				if (left > maxDragRange) {
					left = maxDragRange;
				} else if (left < 0) {
					left = 0;
				}
			}
			return left;
		}

//		@Override
//		public int clampViewPositionVertical(View child, int top, int dy) {
//			return top;
//		}


		//获取水平的拖动范围,默认返回值0,如果重写为大于0的任意值,则helper就会负责处理拦截事件
		@Override
		public int getViewHorizontalDragRange(View child) {
			return maxDragRange;
		}

		//当视图的位置发生改变的时候
		//参数1:被改变的视图
		@Override
		public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
			super.onViewPositionChanged(changedView, left, top, dx, dy);
			//判断在滑动menu的时候,menu向前移动dx,让menu向反方向移动dx,这样menu相当于没有移动
			//在menu上移动dx,将这个dx交给main让main移动
			if (changedView == menu) {
				menu.offsetLeftAndRight(-dx);
				int oldLeft = main.getLeft();
				//计算新的left,限制newLeft,就达到限制newDx的目的
				int newLeft = oldLeft + dx;
				if (newLeft > maxDragRange) {
					newLeft = maxDragRange;
				} else if (newLeft < 0) {
					newLeft = 0;
				}
				int newDx = newLeft - oldLeft;
				main.offsetLeftAndRight(newDx);
			}

			//getleft()的意思Left position of this view relative to its parent.
			float percent = main.getLeft() * 1.0f / maxDragRange;
			executeAnimation(percent);
			//让图片透明度逐渐变化
			changeCurrentState(percent);
		}

		//当释放被捕获的视图的时候
		//参数1:被释放的孩子视图
		//参数2:释放孩子的瞬间的x方向的速度,向左为负,向右为正
		//参数3:释放孩子的瞬间的y方向的速度
		@Override
		public void onViewReleased(View releasedChild, float xvel, float yvel) {
			super.onViewReleased(releasedChild, xvel, yvel);
			//判断速度为0,且释放的左边距大于最大边距的一半,则打开
			if (xvel == 0 && releasedChild.getLeft() > maxDragRange * 0.5f) {
				//打开
				open();
			} else if (xvel > 0) {
				//打开
				open();
			} else {
				//关闭
				close();
			}
		}
	};
	private DragState preState;

	private void changeCurrentState(float percent) {
		preState = currentState;
		if (percent == 0) {
			//关闭
			currentState = DragState.CLOSE;
		} else if (percent == 1) {
			//打开
			currentState = DragState.OPEN;
		} else {
			//拖拽中
			currentState = DragState.DRAGGING;
		}

		if (listener != null) {
			if (currentState == DragState.OPEN) {
				if (currentState != preState) {
//				Log.i("test", "open----------");
					listener.onOpen();
				}
			} else if (currentState == DragState.CLOSE) {
				if (currentState != preState) {
//				Log.i("test", "close----------");
					listener.onClose();
				}
			} else {
//			Log.i("test", "dragging----------");
				//让ImageView,透明度逐渐变化
				listener.onDragging(percent);
//			ImageView iv_head= (ImageView) findViewById(R.id.iv_head);
//			Toast.makeText(getContext(),)
			}
		}
	}

	private OnDragStateChangedListener listener;

	//判断当前控件的状态是否是关闭状态
	public boolean isClose() {
		return currentState==DragState.CLOSE;
	}

	public interface OnDragStateChangedListener {
		void onOpen();

		void onClose();

		void onDragging(float percent);
	}

	public void setOnDragStateChangedListener(OnDragStateChangedListener listener) {
		this.listener = listener;
	}

	private void executeAnimation(float percent) {
		//percent:0-1
		//scale:1-0.8
		//TypeEvaluator:差值器,估值器
		//FloatEvaluator

		//处理main的缩放
		float evaluateResult = evaluate(percent, 1.0f, 0.8f);
		ViewCompat.setScaleX(main, evaluateResult);
		ViewCompat.setScaleY(main, evaluateResult);

		//处理menu的缩放
		evaluateResult = evaluate(percent, 0.6f, 1.0f);
		ViewCompat.setScaleX(menu, evaluateResult);
		ViewCompat.setScaleY(menu, evaluateResult);

		float range = -maxDragRange * 0.8f;
		evaluateResult = evaluate(percent, range, 0);
		ViewCompat.setTranslationX(menu, evaluateResult);
		//颜色从黑色渐变到透明色
		int color = (int) evaluateColor(percent, Color.BLACK, Color.TRANSPARENT);
		//参数2:后来的颜色覆盖之前的颜色
		//外层要有背景,否则getBackground为null
		getBackground().setColorFilter(color, PorterDuff.Mode.SRC_OVER);

	}

	public Object evaluateColor(float fraction, Object startValue, Object endValue) {
		int startInt = (Integer) startValue;
		int startA = (startInt >> 24) & 0xff;
		int startR = (startInt >> 16) & 0xff;
		int startG = (startInt >> 8) & 0xff;
		int startB = startInt & 0xff;

		int endInt = (Integer) endValue;
		int endA = (endInt >> 24) & 0xff;
		int endR = (endInt >> 16) & 0xff;
		int endG = (endInt >> 8) & 0xff;
		int endB = endInt & 0xff;

		return (int) ((startA + (int) (fraction * (endA - startA))) << 24) |
				(int) ((startR + (int) (fraction * (endR - startR))) << 16) |
				(int) ((startG + (int) (fraction * (endG - startG))) << 8) |
				(int) ((startB + (int) (fraction * (endB - startB))));
	}

	//	private int price=1;
//	//表示打开状态
//	private static final int open=1;
//	private static final int close=2;
//	private static final int dragging=3;
//	private static final String OPEN="abc";
//	private String price="abc";
	//枚举里都是常量,但是这个常量不占用任何的基本数据类型的值
	//声明默认状态为关闭
	private DragState currentState = DragState.CLOSE;

	public enum DragState {
		OPEN, CLOSE, DRAGGING
	}

	public float evaluate(float fraction, float startValue, float endValue) {
		return startValue + fraction * (endValue - startValue);
	}

	private void close() {
		if (helper.smoothSlideViewTo(main, 0, 0)) {
			invalidate();
		}
	}

	private void open() {
		//判断menu是否已经移动到了最终的位置
		if (helper.smoothSlideViewTo(main, maxDragRange, 0)) {
			invalidate();
		}
	}

	@Override
	public void computeScroll() {
		super.computeScroll();
		//判断是否存在下一帧,如果有下一帧,移动过去
		if (helper.continueSettling(true)) {
			invalidate();
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		//将触摸事件交由helper处理
		helper.processTouchEvent(event);
		return true;
	}

	//拦截事件
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		//让helper决定是否拦截事件
		return helper.shouldInterceptTouchEvent(ev);
	}

	//在所有的孩子视图加载完成之后回调这个方法
	//只有在这个方法中才能获取所有的孩子
	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
		//代码健壮性处理
		//要求DragLayout有且仅有两个孩子
		if (getChildCount() != 2) {
			throw new RuntimeException("have you follow my suggestion?bastard?fuc?");
		}

		if (!(getChildAt(0) instanceof ViewGroup) || !(getChildAt(1) instanceof ViewGroup)) {
			throw new RuntimeException("Your children must be instance of ViewGroup?You know?");
		}

		menu = getChildAt(0);
		main = getChildAt(1);
	}

	//这个方法是在onMeasure方法执行后执行,可以获取孩子的计算宽高
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		maxDragRange = (int) (main.getMeasuredWidth() * 0.6f);

	}

}

6.activity 处理显示数据和监听事件

public class MainActivity extends AppCompatActivity {

	private ListView lv_main_list;
	private ListView lv_menu_list;
	private ImageView iv_head;
	private DragLayout dl;
	private MainNoTouchLayout main_no_touch_layout;

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

	private void init() {
		initView();
		initDatas();
	}
	private void initDatas() {
		ArrayAdapter<String> adapter=new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,Cheeses.NAMES);
		lv_main_list.setAdapter(adapter);

		adapter=new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,Cheeses.CHEESE_STRINGS){
			//根据每个位置返回对应的视图
			@Override
			public View getView(int position, View convertView, ViewGroup parent) {
				View view = super.getView(position, convertView, parent);
				TextView tv= (TextView) view;
				tv.setTextColor(Color.WHITE);
				return view;
			}
		};
		lv_menu_list.setAdapter(adapter);


		dl.setOnDragStateChangedListener(new DragLayout.OnDragStateChangedListener() {

			@Override
			public void onOpen() {
				lv_menu_list.smoothScrollToPosition(new Random().nextInt(Cheeses.CHEESE_STRINGS.length));
			}

			@Override
			public void onClose() {
				ObjectAnimator oa=ObjectAnimator.ofFloat(iv_head,"translationX",0f,5f);
				oa.setDuration(1000);
				//动画插入器:周期插入器,可以改变动画的执行效果,左右摇摆七次
				oa.setInterpolator(new CycleInterpolator(7));
				oa.start();
			}

			@Override
			public void onDragging(float percent) {
				iv_head.setAlpha(1-percent);
			}
		});

		main_no_touch_layout.setOnMainNoTouchListener(new MainNoTouchLayout.OnMainNoTouchListener() {
			//接口的方法返回值由DragLayout的关闭状态决定
			@Override
			public boolean onMenuClose() {
				return dl.isClose();
			}
		});

	}

	private void initView() {
		lv_main_list = (ListView) findViewById(R.id.lv_main_list);
		lv_menu_list = (ListView) findViewById(R.id.lv_menu_list);
		iv_head = (ImageView) findViewById(R.id.iv_head);
		dl = (DragLayout) findViewById(R.id.dl);
		main_no_touch_layout = (MainNoTouchLayout) findViewById(R.id.main_no_touch_layout);
	}
}

7.定义个常量类

Cheeses{
	
	public static final String[] CHEESE_STRINGS{....}
	public static final String[] NAMES = new String[]{........}

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值