Android自定义组件系列【4】——自定义ViewGroup实现双侧滑动
在上一篇文章《Android自定义组件系列【3】——自定义ViewGroup实现侧滑》中实现了仿Facebook和人人网的侧滑效果,这一篇我们将接着上一篇来实现双面滑动的效果。
1、布局示意图:
2、核心代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec); //获取MyScrollView的宽度
mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取MyScrollView的高度
if(!isLocked){
initX = getScrollX();
isLocked = true;
}
}
在该方法中获取到初始的视图坐标偏移量getScrollX()
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
System.out.println("ACTION_DOWN");
mDownX = x; //记录按下时的x坐标
break;
case MotionEvent.ACTION_UP:
System.out.println("ACTION_UP");
int dis = (int) (x - mDownX); //滑动的距离
if(Math.abs(dis) > (mWidth * mMenuWeight / 2)){
if(dis > 0){ //如果>0则是向右滑动
toRightMove();
}else{ //如果<0则是向左滑动
toLeftMove();
}
}
break;
default:
break;
} return true;
}
监听函数记录下按下和移动的屏幕坐标,求差计算出移动距离,如果这个距离大于阀值 (mWidth * mMenuWeight / 2)则滑动
public void toRightMove(){
System.out.println("maxRight = " + maxRight);
System.out.println("X = " + getScrollX());
if(getScrollX() >= initX){
int dx = (int)(mWidth * mMenuWeight);
mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
if(mListener != null){
mListener.onChanged();
}
invalidate();
}
}
如果是向右滑动则,如果当前是初始位置(centerView在中间)则可以向右滑动(getScrollX == initX),或者当前左边View可以看见,则可以向右滑动将centerView移动到中间(getScrollX > initX).同理有向左滑动的方法。
public void toLeftMove(){
System.out.println("maxLeft = " + maxLeft);
System.out.println("X = " + getScrollX());
if(getScrollX() <= initX){
int dx = (int)(mWidth * mMenuWeight);
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
if(mListener != null){
mListener.onChanged();
}
invalidate();
}
}
3、全部代码
MyScrollView.java
package com.example.testscrollto; import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Scroller; public class MyScrollView extends LinearLayout{ private Context mContext;
private int mWidth;
private int mHeight; private float mMenuWeight = 3.0f / 5; //菜单界面比例 private View mMenuView; //菜单界面
private View mPriView; //内容界面
private View mRightView; //右边界面 private int maxLeft;
private int maxRight;
private int initX; private boolean isLocked = false; private Scroller mScroller; private OnMenuChangedListener mListener; public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mScroller = new Scroller(mContext); } @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mMenuView.layout(-(int)(mWidth * mMenuWeight), 0, 0, mHeight);
mPriView.layout(0, 0, mWidth, mHeight);
mRightView.layout(mWidth, 0, mWidth + (int)(mWidth * mMenuWeight), mHeight);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec); //获取MyScrollView的宽度
mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取MyScrollView的高度
if(!isLocked){
initX = getScrollX();
isLocked = true;
}
} /**设置右滑的菜单View*/
public void setMenu(View menu){
mMenuView = menu;
addView(mMenuView);
} /**
* 设置主界面View
*/
public void setPrimary(View primary){
mPriView = primary;
addView(mPriView);
} public void setRightView(View rightview){
mRightView = rightview;
addView(mRightView);
} private float mDownX; @Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
System.out.println("ACTION_DOWN");
mDownX = x; //记录按下时的x坐标
break;
case MotionEvent.ACTION_UP:
System.out.println("ACTION_UP");
int dis = (int) (x - mDownX); //滑动的距离
if(Math.abs(dis) > (mWidth * mMenuWeight / 2)){
if(dis > 0){ //如果>0则是向右滑动
toRightMove();
}else{ //如果<0则是向左滑动
toLeftMove();
}
}
break;
default:
break;
} return true;
} @Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
} public void toRightMove(){
System.out.println("maxRight = " + maxRight);
System.out.println("X = " + getScrollX());
if(getScrollX() >= initX){
int dx = (int)(mWidth * mMenuWeight);
mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
if(mListener != null){
mListener.onChanged();
}
invalidate();
}
} public void toLeftMove(){
System.out.println("maxLeft = " + maxLeft);
System.out.println("X = " + getScrollX());
if(getScrollX() <= initX){
int dx = (int)(mWidth * mMenuWeight);
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
if(mListener != null){
mListener.onChanged();
}
invalidate();
}
} public interface OnMenuChangedListener{
public void onChanged();
} public void setOnMenuChangedListener(OnMenuChangedListener listener){
mListener = listener;
}
}
MainActivity.java
package com.example.testscrollto; import android.app.Activity;
import android.os.Bundle;
import android.view.View; import com.example.testscrollto.MyScrollView.OnMenuChangedListener; public class MainActivity extends Activity { private MyScrollView mScrollView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mScrollView = (MyScrollView)findViewById(R.id.rightscrollview);
final View menu = getLayoutInflater().inflate(R.layout.rightscrollview_menu, null);
final View primary = getLayoutInflater().inflate(R.layout.rightscrollview_primary, null);
final View rightview = getLayoutInflater().inflate(R.layout.rightscrollview_right_menu, null);
mScrollView.setMenu(menu);
mScrollView.setPrimary(primary);
mScrollView.setRightView(rightview);
mScrollView.setOnMenuChangedListener(new OnMenuChangedListener() { @Override
public void onChanged() {
System.out.println("窗口切换了一次");
}
});
}
}
activity_main.xml
<?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" > <com.example.testscrollto.MyScrollView
android:id="@+id/rightscrollview"
android:layout_width="match_parent"
android:layout_height="match_parent" /> </LinearLayout>
其余三个视图界面无限制,可以自由定义,这里就不贴出来了。
4、运行效果:

源代码下载:http://download.csdn.net/detail/lxq_xsyu/7232701
这样就可以实现左右滑动了,没有任何bug吗?
其实这样看似是没有什么问题了,上面用于判断界面位置的代码在逻辑上看似是对的,但是在实际的应用中偶尔会出现错乱。
下面我们用另外一种方式解决:
package com.example.jaohangui.view; import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Scroller; public class MyScrollLeftRightView extends LinearLayout{ private Scroller mScroller; private View mLeftView; //坐标界面
private View mMainView; //中间主界面
private View mRightView; //右边界面 private float mMeasureWight = 3.0f / 5; //菜单界面比例
private int mWidth;
private int mHeight; private boolean isLocked = false;
private boolean isToLeft = false;
private static int CENTER_PAGE = 1;
private static int LEFT_PAGE = 0;
private static int RIGHT_PAGE = 2;
private int currentPage = CENTER_PAGE; public MyScrollLeftRightView(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mLeftView.layout(-(int)(mWidth * mMeasureWight), 0, 0, mHeight);
mMainView.layout(0, 0, mWidth, mHeight);
mRightView.layout(mWidth, 0, mWidth + (int)(mWidth * mMeasureWight), mHeight);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
} /**
* 添加左边界面内容
* @param view
*/
public void setLeftView(View view){
mLeftView = view;
addView(mLeftView);
} /**
* 添加主界面内容
* @param view
*/
public void setMainView(View view){
mMainView = view;
addView(mMainView);
} /**
* 添加右边界面内容
* @param view
*/
public void setRightView(View view){
mRightView = view;
addView(mRightView);
} private float mDownX;
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = x;
break;
case MotionEvent.ACTION_UP:
int dis = (int)(x - mDownX); //滑动的距离
if(Math.abs(dis) > (mWidth * mMeasureWight / 3)){
if(dis > 0){
toRightMove();
}else{
toLeftMove();
}
}
break; default:
break;
}
return true;
} @Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
isLocked = true;
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}else{
if(currentPage == CENTER_PAGE){
if(isToLeft){
currentPage = RIGHT_PAGE;
}else{
currentPage = LEFT_PAGE;
}
}else{
currentPage = CENTER_PAGE;
}
isLocked = false;
}
} public void toRightMove(){
if(currentPage == LEFT_PAGE || isLocked){
return;
}
int dx = (int)(mWidth * mMeasureWight);
mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
invalidate();
isToLeft = false;
} public void toLeftMove(){
if(currentPage == RIGHT_PAGE || isLocked){
return;
}
System.out.println("ok");
int dx = (int)(mWidth * mMeasureWight);
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
invalidate();
isToLeft = true;
}
}
上面使用了两个用来判断的变量和一个锁定状态(不让进入toLeftMove和toRightMove)的变量。
1、在进入toLeftMove或者toRightMove方法的时候首先会判断是否isLocked为true,如果为true则说明当前是正在滑动状态,不可以执行这两个方法。如果不这样去控制,在界面正在滑动的时候上面的currentPage就会发生错乱。
2、将要滑动之前告诉computeScroll()方法,是从toLeftMove和toRightMove的那个方法中出来的(使用isToLeft)。
3、最后判断Scroller是否已经停止滑动(移动),如果停止则改变当前页面的状态(currentPage的值)
有的时候我们虽然实现了一个功能或者某个逻辑,而如何才能使这段代码更加健壮和有效更值得我们去认真思考。。。
Android自定义组件系列【4】——自定义ViewGroup实现双侧滑动的更多相关文章
- Android自定义组件系列【15】——四个方向滑动的菜单实现
今天无意中实现了一个四个方向滑动的菜单,感觉挺好玩,滑动起来很顺手,既然已经做出来了就贴出来让大家也玩弄一下. 一.效果演示 (说明:目前没有安装Android模拟器,制作的动态图片太卡了,就贴一下静 ...
- Android自定义组件系列【6】——进阶实践(3)
上一篇<Android自定义组件系列[5]--进阶实践(2)>继续对任老师的<可下拉的PinnedHeaderExpandableListView的实现>进行了分析,这一篇计划 ...
- Android自定义组件系列【5】——进阶实践(2)
上一篇<Android自定义组件系列[5]--进阶实践(1)>中对任老师的<可下拉的PinnedHeaderExpandableListView的实现>前一部分进行了实现,这一 ...
- Android自定义组件系列【7】——进阶实践(4)
上一篇<Android自定义组件系列[6]--进阶实践(3)>中补充了关于Android中事件分发的过程知识,这一篇我们接着来分析任老师的<可下拉的PinnedHeaderExpan ...
- Android自定义组件系列【3】——自定义ViewGroup实现侧滑
有关自定义ViewGroup的文章已经很多了,我为什么写这篇文章,对于初学者或者对自定义组件比较生疏的朋友虽然可以拿来主义的用了,但是要一步一步的实现和了解其中的过程和原理才能真真脱离别人的代码,举一 ...
- Android自定义组件系列【13】——Android自定义对话框如此简单
在我们的日常项目中很多地方会用到对话框,但是Android系统为我们提供的对话框样子和我们精心设计的界面很不协调,在这种情况下我们想很自由的定义对话框,或者有的时候我们的对话框是一个图片,没有标题和按 ...
- Android自己定义组件系列【4】——自己定义ViewGroup实现双側滑动
在上一篇文章<Android自己定义组件系列[3]--自己定义ViewGroup实现側滑>中实现了仿Facebook和人人网的側滑效果,这一篇我们将接着上一篇来实现双面滑动的效果. 1.布 ...
- Android自定义组件系列【1】——自定义View及ViewGroup
View类是ViewGroup的父类,ViewGroup具有View的所有特性,ViewGroup主要用来充当View的容器,将其中的View作为自己孩子,并对其进行管理,当然孩子也可以是ViewGr ...
- Android自定义组件系列【12】——非UI线程绘图SurfaceView
一.SurfaceView的介绍 在前面我们已经会自定义View,使用canvas绘图,但是View的绘图机制存在一些缺陷. 1.View缺乏双缓冲机制. 2.程序必须重绘整个View上显示的图片,比 ...
随机推荐
- 通俗理解vuex原理---通过vue例子类比
本文主要通过简单的理解来解释下vuex的基本流程,而这也是vuex难点之一. 首先我们先了解下vuex的作用 vuex其实是集中的数据管理仓库,相当于数据库mongoDB,MySQL等,任何组件都可以 ...
- elasticsearch选举master
数据节点确定了master以后.会开启MasterPinger线程来与其保持心跳通信的逻辑假设通信过程中链接断开,会通过MasterNodeFailureListener监听器进行回调处理处理过程中会 ...
- Nginx日志优化
一 日志轮训切割 [root@centos7 tools]# cat nginx_log.sh #!/bin/bash cd /var/log/nginx/ &&\ /bin/mv a ...
- SqlParameter的用法
SqlParameter的用法 关于Sql注入的基本概念,相信不需多说,大家都清楚,经典的注入语句是' or 1=1--单引号而截断字符串,“or 1=1”的永真式的出现使得表的一些信息被暴露出来,如 ...
- jquery中$(dom).each()和$(dom).length的使用
1.$(dom).each();在dom处理上用的比较多. $(selector).each(function(index,element){ //selector会遍历当前页面里所有匹配的jquer ...
- 学习笔记:Vue——插槽
关于Vue插槽,只用过最简单的语法,现在完整地走一遍官方文档说明,并且探索更多用法. 01.如果组件中没有包含一个<slot>元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃. 0 ...
- 【习题 6-8 UVA - 806】Spatial Structures
[链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 写两个dfs模拟就好. 注意每12个数字输出一个换行.. [代码] /* 1.Shoud it use long long ? 2. ...
- oracle_经常使用分组函数
oracle_经常使用分组函数 ①分组函数 1.max(column):求最大值,对数据类型没有要求,随意数据类型都能够 2.min(column):求最小值,对数据类型没有要求,随意数据类型都 ...
- 快速创建SSH信任实现无密码登录
1. 生成本机的公私钥匙对[oracle@Oracle11_2 scripts]$ -t rsa Generating public/private rsa key pair. Enter file ...
- qemu 参数简介
参数 示例 说明 -hda -hda /data/windows.img 指定windows.img作为硬盘镜像 -cdrom -cdrom /data/windows.iso 指定windows.i ...