QQ测拉效果实现(三)
转载本文请注明出处,尊重原创:
如果想第一时间收到文章更新,可以微信扫描二维码关注我的公众号,或者微信直接搜索“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">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--每一个item-->
<TextView
style="@style/tab_item_style"
android:drawableLeft="@drawable/tab_focus"
android:text="ITEM2"
/>
.....省略
</LinearLayout>
</ScrollView>
<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">
<!--顶部栏-->
<LinearLayout
android:orientation="horizontal"
android:background="@drawable/top_bar_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_menu"
android:src="@drawable/main_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:src="@drawable/top_bar_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android: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>
<!--内容区域-->
<TextView
android:gravity="center"
android:textSize="50dp"
android:textColor="@android:color/black"
android:text="ITEM1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
然后自定义SlidingMenu继承自ViewGroup:
public class SlidingMenu extends ViewGroup {
public SlidingMenu(Context context) {
this(context,null);
}
public SlidingMenu(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
}
<?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:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.itydl.slidingmenu.MainActivity">
<com.itydl.slidingmenu.SlidingMenu
android: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>
接下来的任务就是开发自定义控件了:
重写onMeasure();onLayout();方法:
/**
* XML解析完成调用此方法
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mLeftMenu = getChildAt(0);
mMainContent = getChildAt(1);
mLeftWidth = mLeftMenu.getLayoutParams().width;
// Log.e(TAG,"mLeftWidth : --->"+mLeftWidth);
}
/**
* 测量孩子控件的大小
*/
@Override
protected 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);
}
/**
* 布局孩子控件
*/
@Override
protected 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);
}
但是这里跟上一篇的有点不一样,比如getScrollX(),上一篇的值如下:
它是一个正数,是因为上篇的自定义View继承的是HorizontalScrollView,它的计算是从整个绿色区域的左上角开始的。意思是说,往右滑动,getScrollX()的值变小,往左滑动,getScrollX()的值变大。如上图所示
而本篇文章的布局是直接继承自ViewGroup,而且布局中
这里的SlidingMenu跟屏幕一样,所以计算应该是从红色区域左上角计算的,比如,这里的getScrollX():
然后移动一点:
再看一张:
相信上面几张图,能明白这里跟上一篇的区别。
然后往下,重写onTouchEvent()进行交互事件:
@Override
public 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;
}
然后同理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();//当前x
int 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
}
@Override
public 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;
这个方法有五个参数,分别是: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,做好判断,如果是横滑,就让时间拦截掉,交给自己处理:
@Override
public 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小菜”。
上一篇: Angular实现悬浮球组件
下一篇: CSS中 display的相关属性
推荐阅读
-
如何有效的引入流量?通过微信公众号和QQ群营销等第三方比发外链效果更好
-
Android使用ViewDragHelper实现QQ6.X最新版本侧滑界面效果实例代码
-
细说第三方登录—>PHP实现支付宝、QQ、微博、百度账号、Github登录
-
Android编程实现仿QQ发表说说,上传照片及弹出框效果【附demo源码下载】
-
iOS粒子路径移动效果 iOS实现QQ拖动效果
-
ios基于MJRefresh实现上拉刷新和下拉加载动画效果
-
iOS QQ第三方登录实现
-
iOS粒子路径移动效果 iOS实现QQ拖动效果
-
ios基于MJRefresh实现上拉刷新和下拉加载动画效果
-
WPF自定义TreeView控件样式实现QQ联系人列表效果