转载本文请注明出处,尊重原创:

如果想第一时间收到文章更新,可以微信扫描二维码关注我的公众号,或者微信直接搜索“Android小菜”进行关注,所有的文章会比CSDN更快一步:

前两篇通过HorizontalScrollView + LinearLayout + scrollTo + 属性动画的知识实现了一个仿QQ5.0效果的控件。本篇纯手工实现类似的测拉效果。

先看最终要实现什么样的效果:

基本要点:自定义ViewGroup类型控件,需要两块布局分别是左侧菜单left和右侧主内容区域content,然后分别测量两边的大小以及布局,重写onTouchEvent方法进行事件的交互,使用Scroll进行平滑移动。

先把架构搭起来:

left布局:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="200dp"android:background="@drawable/menu_bg"android:layout_height="match_parent"><LinearLayoutandroid:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><!--每一个item--><TextViewstyle="@style/tab_item_style"android:drawableLeft="@drawable/tab_focus"android:text="ITEM2"/>.....省略</LinearLayout></ScrollView>

left布局文件很简单,外层嵌套ScrollView,内部放置TextView作为测拉菜单的item项。然后每个item都一样,所以抽取了样式:

<style name="tab_item_style"><item name="android:background">@drawable/tab_item_selector</item><item name="android:gravity">center_vertical</item><item name="android:drawablePadding">20dp</item><item name="android:paddingBottom">15dp</item><item name="android:paddingTop">15dp</item><item name="android:paddingLeft">20dp</item><item name="android:textSize">24sp</item><item name="android:textColor">@android:color/white</item><item name="android:layout_width">match_parent</item><item name="android:layout_height">wrap_content</item>
</style>

上面代码展示效果如下:

然后是主页面内容

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><!--顶部栏--><LinearLayoutandroid:orientation="horizontal"android:background="@drawable/top_bar_bg"android:layout_width="match_parent"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/iv_menu"android:src="@drawable/main_back"android:layout_width="wrap_content"android:layout_height="wrap_content"/><ImageViewandroid:src="@drawable/top_bar_divider"android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextViewandroid:paddingLeft="65dp"android:layout_gravity="center"android:textSize="25sp"android:textColor="@android:color/white"android:text="主页面"android:layout_width="match_parent"android:layout_height="wrap_content"/></LinearLayout><!--内容区域--><TextViewandroid:gravity="center"android:textSize="50dp"android:textColor="@android:color/black"android:text="ITEM1"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>

它也很简答,一个标题栏,一个TextView,如下:

然后自定义SlidingMenu继承自ViewGroup:

public class SlidingMenu extends ViewGroup {public SlidingMenu(Context context) {this(context,null);}public SlidingMenu(Context context, AttributeSet attrs) {super(context, attrs);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {}
}

然后在activity_main布局里面加入我们的控件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/activity_main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.itydl.slidingmenu.MainActivity"><com.itydl.slidingmenu.SlidingMenuandroid:background="@color/colorAccent"android:id="@+id/slidingmenu"android:layout_width="match_parent"android:layout_height="match_parent"><!--左侧菜单--><include layout="@layout/left"/><!--右侧内容--><include layout="@layout/content"/></com.itydl.slidingmenu.SlidingMenu></RelativeLayout>

此时运行程序后,是不会显示任何东西。为了能更好的理解,我在 SlidingMenu布局里面加了一个背景,此时运行,肯定能让SlidingMenu控件展示出来,但是他里面的孩子控件不会正常展示:下图为手机运行后效果:

接下来的任务就是开发自定义控件了:

重写onMeasure();onLayout();方法:

/*** XML解析完成调用此方法*/@Overrideprotected void onFinishInflate() {super.onFinishInflate();mLeftMenu = getChildAt(0);mMainContent = getChildAt(1);mLeftWidth = mLeftMenu.getLayoutParams().width;
//        Log.e(TAG,"mLeftWidth : --->"+mLeftWidth);}/*** 测量孩子控件的大小*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//测量左侧int  leftWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mLeftWidth,MeasureSpec.EXACTLY);mLeftMenu.measure(leftWidthMeasureSpec,heightMeasureSpec);//测量右侧mMainContent.measure(widthMeasureSpec,heightMeasureSpec);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}/*** 布局孩子控件*/@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {
//        Log.e(TAG,"mLeftMenu.getMeasuredWidth() : --->"+mLeftMenu.getMeasuredWidth());
//        Log.e(TAG,"mMainContent.getMeasuredWidth() : --->"+mMainContent.getMeasuredWidth());mLeftMenu.layout(-mLeftMenu.getMeasuredWidth(),0,0,mLeftMenu.getMeasuredHeight());Log.e(TAG,"this.width : --->"+this.getMeasuredWidth());mMainContent.layout(0,0,r,b);}

以上重写了三个方法,而且特别的简单,在 onFinishInflate() 方法在xml解析完成后调用,在里面可以拿到控件的实例。然后onMeasure();里面对控件做了测量,因为ViewGroup类型的自定义View需要对孩子控件测量大小,最终才会确认自己的大小。在这里,由于左侧菜单的宽度是自定义的一个宽度(比如上面写了240dp),而高度跟自定义View的高度是一样的,所以可以沿用我们自定义View的高度;然后右侧主内容就完全跟咱们的自定义View的宽高是一样的了,所以宽高都可以沿用自定义View的宽高。然后是 onLayout();方法,在这里面对控件进行了布局,这三个方法执行完毕后可以在手机上展示,上面可以用下面的图来表示出来:

但是这里跟上一篇的有点不一样,比如getScrollX(),上一篇的值如下:

它是一个正数,是因为上篇的自定义View继承的是HorizontalScrollView,它的计算是从整个绿色区域的左上角开始的。意思是说,往右滑动,getScrollX()的值变小,往左滑动,getScrollX()的值变大。如上图所示

而本篇文章的布局是直接继承自ViewGroup,而且布局中

这里的SlidingMenu跟屏幕一样,所以计算应该是从红色区域左上角计算的,比如,这里的getScrollX():

然后移动一点:

再看一张:

相信上面几张图,能明白这里跟上一篇的区别。

然后往下,重写onTouchEvent()进行交互事件:

@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mDownX = event.getX();break;case MotionEvent.ACTION_MOVE:float mMoveX = event.getX();int diffX = (int) (mDownX - mMoveX + 0.5f);//四舍五入Log.e(TAG,"diffX : --->"+diffX);int scrolledX = getScrollX() + diffX;Log.e(TAG,"getScrollX() : --->"+getScrollX());if(scrolledX < -mLeftWidth){scrollTo(-mLeftWidth,0);}else if(scrolledX > 0){scrollTo(0,0);}else{scrollBy(diffX,0);}mDownX = mMoveX;//保存移动后上一次的值break;case MotionEvent.ACTION_UP:Log.e(TAG,"ACTION_UP : --->");break;default:break;}return true;}

重写 onTouchEvent()方法,返回值写为true,为了保证down事件以后,move和up事件都能顺利得到(down一旦返回值为了false,后序的move、up事件都不再调用)。上面的获取坐标的方式相信不用再去再做解释,主要在于scrollBy(x,y)和scrollTo(x,y)相关。主要是对边界的处理,不让一直滑动而出现滑动出界面的难看局面。比如scrolledX < -mLeftWidth 图形如下:这时候应该要让控件完全展示左侧菜单就截止。scrollTo(-mLeftWidth,0);

然后同理scrolledX > 0 。此时也属于越界,那么直接调用scrollTo(0,0);让控件回到(0,0)点。

int scrolledX = getScrollX() + diffX; 是为了解决边界出现“晃动”现象。

出现这种现象的原因是:如图

比如此时getScrollX为-mLeftWidth,此时继续move事件,往右滑动,此时的代码走红笔标注位置:

显然又往右滑动了一些距离,然后继续滑动,发现此时scrolledX<-mLeftWidth成立了,则会调用scrollTo(-mLeftWidth,0);

因此要时刻记录下最新的滑动了的位置,int scrolledX = getScrollX() + diffX;  比如当getScrollX() 为-mLeftWidth但是移动的距离也加上了,则若是往右滑动,这个值肯定满足scrolledX<-mLeftWidth ,这样就不会出现“晃动”效果了。

然后再往下,解决UP事件,UP的时候要让控件判断位置,过度到某个状态:

此时运行:

完成了基本的测拉效果,往下就是平滑过度一些。当然是使用Scroller

修改UP事件:

然后,写一个方法:switchMenu():以及其他方法:

    /*** 切换左侧菜单的展示与隐藏*/private void switchMenu() {int startX = getScrollX();//当前xint dx = 0;if(CURRENT_STATE == CURRENT_CLOSE){//切换到关闭态dx = 0 - startX;}else if(CURRENT_STATE == CURRENT_OPEN){dx = -mLeftWidth - startX;}/*** 第三个值:目标 - startX;*/scroller.startScroll(startX, 0, dx, 0, Math.abs(dx) * 5);//时间不确定,根据移动个数来算。Math.abs(dx)对dx取绝对值invalidate();// invalidate -> drawChild -> child.draw -> computeScroll}@Overridepublic void computeScroll() {// 当scroller模拟完毕数据时,不再调用invalidate,if(scroller.computeScrollOffset()) {int currX = scroller.getCurrX();scrollTo(currX, 0);//这能调用一次,因此要循环调用invalidate();invalidate();//递归调用}}/*** 调用方来控制左侧菜单的打开与关闭*/public void toogle(){if(CURRENT_STATE == CURRENT_OPEN){//关闭close();}else{//打开open();}switchMenu();}/*** 打开左侧菜单*/private void open(){CURRENT_STATE = CURRENT_OPEN;}/*** 关闭左侧菜单*/private void close(){CURRENT_STATE = CURRENT_CLOSE;}

可以看到是通过两个变量来表示当前的状态是打开还是隐藏左侧菜单,

   /*** 当前侧滑菜单处于打开状态*/private static final int CURRENT_OPEN = 1;/*** 当前侧滑菜单处于打关闭状态*/private static final int CURRENT_CLOSE = 2;/*** 当前策划菜单的状态,默认处于关闭状态*/private static int CURRENT_STATE = CURRENT_CLOSE;

然后统一调用 switchMenu()方法,在这个方法里面使用创建的Scroller对象,这个对象会帮助我们做平滑移动(它内部是把距离不断的切分,然后再去调用scrollTo完成的)然后调用scroller.startScroll(startX, 0, dx, 0, Math.abs(dx) * 5);

这个方法有五个参数,分别是:1、移动的x起点位置;2、移动的y起点位置;3、距离差dx(等于目标值-起始值);4、代表y;5、时间差。

Math.abs(dx) * 5能让时间均衡,移动均衡。

然后调用invalidate();方法,它的执行流程是: invalidate -> drawChild -> child.draw -> computeScroll,然后重写了computeScroll方法,在他里面能拿到切分整个距离的当前值坐标。 int currX = scroller.getCurrX();拿到当前的x值,然后不断的调用scrollTo(currX, 0);进行移动到该值.打印一段log如下:
          -8,-5,-4,-3,-1,-1,0

最后再运行程序:

此时所有的功能基本上已经实现了,但是,我们会发现,左侧菜单是ScrollView,竖向滑动可以实现,但是横向滑动,却没有任何效果。因此要解决滑动冲突:

其实很简单,只需要重写onInterceptTouchEvent,做好判断,如果是横滑,就让时间拦截掉,交给自己处理:

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:mDownX = ev.getX();break;case MotionEvent.ACTION_MOVE:float moveX = ev.getX();int dx = (int) Math.abs(moveX - mDownX + 0.5);if(dx > dip2px(mContext,8)){//如果是横滑,事件拦截掉,交给自己处理return true;}break;default:break;}return super.onInterceptTouchEvent(ev);}

最后再运行程序:

到此所有知识就讲解完毕了,喜欢可以点个赞。或者加个关注。

想要及时获取最新文章,请关注微信公众号:“Android小菜”。

QQ测拉效果实现(三)相关推荐

  1. 自定义View 篇四《低仿QQ测拉删除》

    都知道QQ有一个比较牛逼的效果就是测拉删除效果,目前这个功能,网上自定义控件也有很多实现方式了,本篇也自己实现一个测拉删除效果的自定义控件.虽然功能一样,实现方式不同罢了,也希望提供一些思路,对自己和 ...

  2. Android仿苹果版QQ下拉刷新实现(三)

    前言 第三篇下拉刷新的博客来的稍微有点晚,因为前两篇的博客访问量一直不是很高,所以博主花了点时间修改了整体的Demo效果,处理了很多极端下拉情况下的显示问题,给大家呈现一个完美的下拉刷新控件.因为本文 ...

  3. android实现微信网页浏览、QQ下拉效果SlidingLayout

    SlidingLayout是一种Android平台的View控件,可以帮助你实现类似微信网页浏览的下拉功能,也可以帮助你实现类似iOS中UITableView的下拉上拉弹跳的果冻效果. Sliding ...

  4. Android仿苹果版QQ下拉刷新实现(二) ——贝塞尔曲线开发鼻涕下拉粘连效果

    前言 接着上一期 Android仿苹果版QQ下拉刷新实现(一) --打造简单平滑的通用下拉刷新控件 的博客开始,同样,在开始前我们先来看一下目标效果: 下面上一下本章需要实现的效果图: 大家看到这个效 ...

  5. qq视频转码失败怎么办_测试音视频,究竟测什么?(三)

    我们继续来讲测试音视频究竟测什么第三部分内容. 流媒体类业务 流媒体业务是音视频技术的重要分支.由于我从事的谈话常年类业务,也许这是一个没有多少资格来提出建议.但因为这个重要部分是介绍自己不同企业业务 ...

  6. JavaScript经典效果集锦(三)

    JavaScript经典效果集锦(三) 二十一 类似与google个性页面的好东东------网友155120 二十二 漂亮的表格 二十三 经典的带阴影的可拖动的浮动层------网友marvello ...

  7. Android仿苹果版QQ下拉刷新实现(一) ——打造简单平滑的通用下拉刷新控件

    前言: 因为公司人员变动原因,导致了博主四个月没有动安卓,一直在做IOS开发,如今接近年前,终于可以花一定的时间放在安卓上了.好了,废话不多说,今天我们要带来的效果是苹果版本的QQ下拉刷新.首先看一下 ...

  8. Android开发之使用贝塞尔曲线实现黏性水珠下拉效果

    Android开发之使用贝塞尔曲线实现黏性水珠下拉效果 标签: 贝塞尔曲线 简介 网上关于贝塞尔曲线的博客和教程很多,通常讲到的三点确定一条曲线:起点,终点,辅助点. 常见的贝塞尔黏性效果 常见的各阶 ...

  9. WEBGIS开发 模型抽拉效果实现 Cesium EarthSDK

    项目场景: 最近一直在学习CesiumLab工具的使用,除了原生的Cesium以外,顺带也学习了一下CesiumLab出的EarthSDK库,这个库用了一下发现也十分的好用,封装了很多效果可以直接调用 ...

最新文章

  1. Js弹性漂浮广告代码
  2. 控件中的Events个人理解。
  3. Python 进程锁使用
  4. SpringBoot:Spring boot 主程序的功能SpringApplication.run(启动流程)
  5. php sql 中文编码,php sql如何设置编码
  6. WTEditor(windows窗口标题栏文字修改工具)绿色单文件版V1.0 | windows窗口标题文字怎么修改?
  7. 云控微信开发SDK使用教程--手机微信群聊删除通知服务端
  8. 80004005 mysql_数据库报错80004005
  9. 【设计模式】:单例设计模式深究
  10. UE5 live-coding和build中version“1.2“ not support build failed解决方法
  11. 手机如何快速转换图片格式?改图片格式手机如何操作?
  12. python list转josn,以及读写txt、json文件
  13. uview去除u-button按钮自带边框细线
  14. html调用手机NFC,NFC门禁模拟-教你用NFC手机模拟门禁卡
  15. win10下速腾聚创RS-Lidar-32配置教程
  16. Vi编辑器的使用方法及用vi编辑器编写一个C程序
  17. CentOS 7 使用RPM一键离线安装 GCC+tcpdump 环境
  18. Scaling Instruction-Finetuned Language Models翻译
  19. html5 商品分类页面效果zepto
  20. jacob 字体设置

热门文章

  1. excel打印预览在哪里_Excel常用打印方法汇总
  2. commonjs使用 范例
  3. r5 3500u和r5 4600u的区别
  4. 多态Person p=new Student();
  5. 阿瑞斯(ARS)创世公链开创区块链信息技术新时代
  6. linux到指定目录,linux移动文件到指定目录操作方法
  7. vscode删除当前行快捷键
  8. 江苏五年制专转本报名要求?哪些同学有资格报名专转本?
  9. @property的三类属性
  10. Redis的使用场景