自定义控件之DragLayout仿QQ界面

2024-04-07 11:18

本文主要是介绍自定义控件之DragLayout仿QQ界面,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns: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.DragLayoutandroid: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"><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/head"/><ListViewandroid: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"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="#18B4ED"android:orientation="vertical"android:padding="10dp"><ImageViewandroid:id="@+id/iv_head"android:layout_width="50dp"android:layout_height="50dp"android:src="@drawable/head"/></LinearLayout><ListViewandroid: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() {}@Overridepublic boolean onTouchEvent(MotionEvent event) {return true;}//判断DragLayout的状态@Overridepublic 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@Overridepublic boolean tryCaptureView(View child, int pointerId) {return child == main || child == menu;}//水平滑动的处理//参数1:被捕获的孩子视图//参数2:oldLeft+dx=targetLeft,新的left值//参数3:dx,系统每隔一段时间检测手指的移动的距离@Overridepublic 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就会负责处理拦截事件@Overridepublic int getViewHorizontalDragRange(View child) {return maxDragRange;}//当视图的位置发生改变的时候//参数1:被改变的视图@Overridepublic 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方向的速度@Overridepublic 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为nullgetBackground().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();}}@Overridepublic void computeScroll() {super.computeScroll();//判断是否存在下一帧,如果有下一帧,移动过去if (helper.continueSettling(true)) {invalidate();}}@Overridepublic boolean onTouchEvent(MotionEvent event) {//将触摸事件交由helper处理helper.processTouchEvent(event);return true;}//拦截事件@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {//让helper决定是否拦截事件return helper.shouldInterceptTouchEvent(ev);}//在所有的孩子视图加载完成之后回调这个方法//只有在这个方法中才能获取所有的孩子@Overrideprotected 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方法执行后执行,可以获取孩子的计算宽高@Overrideprotected 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;@Overrideprotected 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){//根据每个位置返回对应的视图@Overridepublic 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() {@Overridepublic void onOpen() {lv_menu_list.smoothScrollToPosition(new Random().nextInt(Cheeses.CHEESE_STRINGS.length));}@Overridepublic void onClose() {ObjectAnimator oa=ObjectAnimator.ofFloat(iv_head,"translationX",0f,5f);oa.setDuration(1000);//动画插入器:周期插入器,可以改变动画的执行效果,左右摇摆七次oa.setInterpolator(new CycleInterpolator(7));oa.start();}@Overridepublic void onDragging(float percent) {iv_head.setAlpha(1-percent);}});main_no_touch_layout.setOnMainNoTouchListener(new MainNoTouchLayout.OnMainNoTouchListener() {//接口的方法返回值由DragLayout的关闭状态决定@Overridepublic 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[]{........}

}


这篇关于自定义控件之DragLayout仿QQ界面的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/882452

相关文章

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

如何自定义Nginx JSON日志格式配置

《如何自定义NginxJSON日志格式配置》Nginx作为最流行的Web服务器之一,其灵活的日志配置能力允许我们根据需求定制日志格式,本文将详细介绍如何配置Nginx以JSON格式记录访问日志,这种... 目录前言为什么选择jsON格式日志?配置步骤详解1. 安装Nginx服务2. 自定义JSON日志格式各

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

基于Spring实现自定义错误信息返回详解

《基于Spring实现自定义错误信息返回详解》这篇文章主要为大家详细介绍了如何基于Spring实现自定义错误信息返回效果,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录背景目标实现产出背景Spring 提供了 @RestConChina编程trollerAdvice 用来实现 HTT

SpringSecurity 认证、注销、权限控制功能(注销、记住密码、自定义登入页)

《SpringSecurity认证、注销、权限控制功能(注销、记住密码、自定义登入页)》SpringSecurity是一个强大的Java框架,用于保护应用程序的安全性,它提供了一套全面的安全解决方案... 目录简介认识Spring Security“认证”(Authentication)“授权” (Auth

SpringBoot自定义注解如何解决公共字段填充问题

《SpringBoot自定义注解如何解决公共字段填充问题》本文介绍了在系统开发中,如何使用AOP切面编程实现公共字段自动填充的功能,从而简化代码,通过自定义注解和切面类,可以统一处理创建时间和修改时间... 目录1.1 问题分析1.2 实现思路1.3 代码开发1.3.1 步骤一1.3.2 步骤二1.3.3

使用Java发送邮件到QQ邮箱的完整指南

《使用Java发送邮件到QQ邮箱的完整指南》在现代软件开发中,邮件发送功能是一个常见的需求,无论是用户注册验证、密码重置,还是系统通知,邮件都是一种重要的通信方式,本文将详细介绍如何使用Java编写程... 目录引言1. 准备工作1.1 获取QQ邮箱的SMTP授权码1.2 添加JavaMail依赖2. 实现

dubbo3 filter(过滤器)如何自定义过滤器

《dubbo3filter(过滤器)如何自定义过滤器》dubbo3filter(过滤器)类似于javaweb中的filter和springmvc中的intercaptor,用于在请求发送前或到达前进... 目录dubbo3 filter(过滤器)简介dubbo 过滤器运行时机自定义 filter第一种 @A

CSS自定义浏览器滚动条样式完整代码

《CSS自定义浏览器滚动条样式完整代码》:本文主要介绍了如何使用CSS自定义浏览器滚动条的样式,包括隐藏滚动条的角落、设置滚动条的基本样式、轨道样式和滑块样式,并提供了完整的CSS代码示例,通过这些技巧,你可以为你的网站添加个性化的滚动条样式,从而提升用户体验,详细内容请阅读本文,希望能对你有所帮助...

C#实现WinForm控件焦点的获取与失去

《C#实现WinForm控件焦点的获取与失去》在一个数据输入表单中,当用户从一个文本框切换到另一个文本框时,需要准确地判断焦点的转移,以便进行数据验证、提示信息显示等操作,本文将探讨Winform控件... 目录前言获取焦点改变TabIndex属性值调用Focus方法失去焦点总结最后前言在一个数据输入表单