自定义控件之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

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla

lvgl8.3.6 控件垂直布局 label控件在image控件的下方显示

在使用 LVGL 8.3.6 创建一个垂直布局,其中 label 控件位于 image 控件下方,你可以使用 lv_obj_set_flex_flow 来设置布局为垂直,并确保 label 控件在 image 控件后添加。这里是如何步骤性地实现它的一个基本示例: 创建父容器:首先创建一个容器对象,该对象将作为布局的基础。设置容器为垂直布局:使用 lv_obj_set_flex_flow 设置容器

HTML5自定义属性对象Dataset

原文转自HTML5自定义属性对象Dataset简介 一、html5 自定义属性介绍 之前翻译的“你必须知道的28个HTML5特征、窍门和技术”一文中对于HTML5中自定义合法属性data-已经做过些介绍,就是在HTML5中我们可以使用data-前缀设置我们需要的自定义属性,来进行一些数据的存放,例如我们要在一个文字按钮上存放相对应的id: <a href="javascript:" d

一步一步将PlantUML类图导出为自定义格式的XMI文件

一步一步将PlantUML类图导出为自定义格式的XMI文件 说明: 首次发表日期:2024-09-08PlantUML官网: https://plantuml.com/zh/PlantUML命令行文档: https://plantuml.com/zh/command-line#6a26f548831e6a8cPlantUML XMI文档: https://plantuml.com/zh/xmi

argodb自定义函数读取hdfs文件的注意点,避免FileSystem已关闭异常

一、问题描述 一位同学反馈,他写的argo存过中调用了一个自定义函数,函数会加载hdfs上的一个文件,但有些节点会报FileSystem closed异常,同时有时任务会成功,有时会失败。 二、问题分析 argodb的计算引擎是基于spark的定制化引擎,对于自定义函数的调用跟hive on spark的是一致的。udf要通过反射生成实例,然后迭代调用evaluate。通过代码分析,udf在

一款支持同一个屏幕界面同时播放多个视频的视频播放软件

GridPlayer 是一款基于 VLC 的免费开源跨平台多视频同步播放工具,支持在一块屏幕上同时播放多个视频。其主要功能包括: 多视频播放:用户可以在一个窗口中同时播放任意数量的视频,数量仅受硬件性能限制。支持多种格式和流媒体:GridPlayer 支持所有由 VLC 支持的视频格式以及流媒体 URL(如 m3u8 链接)。自定义网格布局:用户可以配置播放器的网格布局,以适应不同的观看需求。硬

小程序button控件上下边框的显示和隐藏

问题 想使用button自带的loading图标功能,但又不需要button显示边框线 button控件有一条淡灰色的边框,在控件上了样式 border:none; 无法让button边框隐藏 代码如下: <button class="btn">.btn{border:none; /*一般使用这个就是可以去掉边框了*/} 解决方案 发现button控件有一个伪元素(::after