Android自定义组件系列【3】——自定义ViewGroup实现侧滑
有关自定义ViewGroup的文章已经很多了,我为什么写这篇文章,对于初学者或者对自定义组件比较生疏的朋友虽然可以拿来主义的用了,但是要一步一步的实现和了解其中的过程和原理才能真真脱离别人的代码,举一反三却不容易,很多博主其实不愿意一步一步的去写,这样很耗时,但是如果能对读者有帮助,能从这篇文章中学会自定义组件就达到我的目的了。
第一步:搭建框架来实现一个3/5和2/5分屏的界面,效果如下:
最外层是一个自定义的ViewGroup布局文件如下:
package com.example.testscrollto; import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.MeasureSpec; public class MyScrollView extends ViewGroup{ private int mWidth;
private int mHeight; private float mMenuWeight = 3.0f / 5; //菜单界面比例 private View mMenuView; //菜单界面
private View mPriView; //内容界面 public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs); } @Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
System.out.println("执行了onLayout");
mMenuView.layout(0, 0, (int)(mWidth * mMenuWeight), mHeight);
mPriView.layout((int)(mWidth * mMenuWeight), 0, mWidth, mHeight);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
System.out.println("执行了onMeasure");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/*
* onMeasure传入的两个参数是由上一层控件传入的大小,有多种情况,重写该方法时需要对计算控件的实际大小,
* 然后调用setMeasuredDimension(int, int)设置实际大小。
* onMeasure传入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值,而是将模式和尺寸组合在一起的数值。
* 我们需要通过int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,
* 用int size = MeasureSpec.getSize(widthMeasureSpec)得到尺寸。
* mode共有三种情况,取值分别为MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。
* MeasureSpec.EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。
* MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。
* MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。
*/
mWidth = MeasureSpec.getSize(widthMeasureSpec); //获取MyScrollView的宽度
mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取MyScrollView的高度
} /**设置右滑的菜单View*/
public void setMenu(View menu){
mMenuView = menu;
addView(mMenuView);
} /**
* 设置主界面View
*/
public void setPrimary(View primary){
mPriView = primary;
addView(mPriView);
} }
第二步:按需要设置界面位置
@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
mMenuView.layout(-(int)(mWidth * mMenuWeight), 0, 0, mHeight);
mPriView.layout(0, 0, mWidth, mHeight);
}
第三步:实现左右滑动
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.Scroller; public class MyScrollView extends ViewGroup{ private Context mContext;
private int mWidth;
private int mHeight; private float mMenuWeight = 3.0f / 5; //菜单界面比例 private View mMenuView; //菜单界面
private View mPriView; //内容界面 private boolean mIsShowMenu; private Scroller mScroller; public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mScroller = new Scroller(mContext);
} @Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
mMenuView.layout(-(int)(mWidth * mMenuWeight), 0, 0, mHeight);
mPriView.layout(0, 0, mWidth, mHeight);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec); //获取MyScrollView的宽度
mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取MyScrollView的高度
} /**设置右滑的菜单View*/
public void setMenu(View menu){
mMenuView = menu;
addView(mMenuView);
} /**
* 设置主界面View
*/
public void setPrimary(View primary){
mPriView = primary;
addView(mPriView);
} 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则是向右滑动
showMenu();
}else{ //如果<0则是向左滑动
hideMenu();
}
}
break;
default:
break;
} return true;
} @Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
} public boolean isShowMenu(){
return mIsShowMenu;
} public void showMenu(){
if(mIsShowMenu){
return;
}
mIsShowMenu = true; //标记菜单已经显示
int dx = (int)(mWidth * mMenuWeight); //滑动到目标位置的距离
mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
invalidate();
} public void hideMenu(){
if(!mIsShowMenu){
return;
}
mIsShowMenu = false;
int dx = (int)(mWidth * mMenuWeight);
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
invalidate();
} }
从上面代码中可以看到下面两句代码触发computeScroll()方法
mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
invalidate();
第四步:添加窗口状态切换监听接口
public interface OnMenuChangedListener{
public void onChanged(boolean isShow);
} public void setOnMenuChangedListener(OnMenuChangedListener listener){
mListener = listener;
}
将showMenu()方法和hideMenu()方法修改如下:
public void showMenu(){
if(mIsShowMenu){
return;
}
mIsShowMenu = true;
int dx = (int)(mWidth * mMenuWeight);
mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
if(mListener != null){
mListener.onChanged(mIsShowMenu);
}
invalidate();
} public void hideMenu(){
if(!mIsShowMenu){
return;
}
mIsShowMenu = false;
int dx = (int)(mWidth * mMenuWeight);
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
if(mListener != null){
mListener.onChanged(mIsShowMenu);
}
invalidate();
}
在MainActivity中添加监听
mScrollView.setOnMenuChangedListener(new OnMenuChangedListener() { @Override
public void onChanged(boolean isShow) {
System.out.println("窗口切换了一次");
}
});
第五步:根据具体业务及需求实现菜单列表及界面(直接贴出代码)
MainActivity.java
package com.example.testrefreshview; import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView; import com.example.testrefreshview.RightScrollView.OnMenuChangedListener; /**
* 测试具有右滑菜单功能的ViewGroup,RigthScrollView
*@author Lqh
*/
public class MainActivity extends Activity { private RightScrollView mRightScrollView;
private Button mShowMenuBtn;
private ListView mMenuList;
private ArrayAdapter<String> mAdapter;
private String[] menus = {"附近的人", "我的资料", "设置", "游戏", "即时聊天"}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.rightscrollview_test);
mRightScrollView = (RightScrollView)findViewById(R.id.rightscrollview);
final View menu = getLayoutInflater().inflate(R.layout.rightscrollview_menu, null);
final View primary = getLayoutInflater().inflate(R.layout.rightscrollview_primary, null);
mMenuList = (ListView) menu.findViewById(R.id.list_right_menu);
mShowMenuBtn = (Button) primary.findViewById(R.id.btn_showmenu);
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, menus);
mMenuList.setAdapter(mAdapter); mShowMenuBtn.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if(mRightScrollView.isShowMenu()){
mRightScrollView.hideMenu();
}else{
mRightScrollView.showMenu();
}
}
}); mRightScrollView.setOnMenuChangedListener(new OnMenuChangedListener() {
public void onChanged(boolean isShow) {
if(isShow){
mShowMenuBtn.setText("隐藏菜单");
}else{
mShowMenuBtn.setText("显示菜单");
}
}
}); mRightScrollView.setMenu(menu);
mRightScrollView.setPrimary(primary); mMenuList.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
switch(position){
case 0:
primary.setBackgroundColor(Color.CYAN);
break;
case 1:
primary.setBackgroundColor(Color.BLUE);
break;
case 2:
primary.setBackgroundColor(Color.GRAY);
break;
case 3:
primary.setBackgroundColor(Color.MAGENTA);
break;
case 4:
primary.setBackgroundColor(Color.YELLOW);
break;
}
mRightScrollView.hideMenu();
}
}); }
}
RightScrollView.java
package com.example.testrefreshview; import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller; /**
* 具有右滑菜单的ViewGroup,类似于Facebook的主界面
* @author Lqh
*/
public class RightScrollView extends ViewGroup { private Context mContext;
private Scroller mScroller;
private View mMenuView;
private View mPriView;
private int mWidth;
private int mHeight;
private boolean mIsShowMenu;
private float mMenuWeight = 3.0f / 5;
private OnMenuChangedListener mListener; public RightScrollView(Context context) {
this(context, null);
} public RightScrollView(Context context, AttributeSet attrs) {
super(context, attrs); mContext = context;
mScroller = new Scroller(mContext); } /**设置右滑的菜单View*/
public void setMenu(View menu){
mMenuView = menu;
addView(mMenuView);
} /**
* 设置主界面View
*/
public void setPrimary(View primary){
mPriView = primary;
addView(mPriView);
} public boolean isShowMenu(){
return mIsShowMenu;
} public void setOnMenuChangedListener(OnMenuChangedListener listener){
mListener = listener;
} public void showMenu(){
if(mIsShowMenu){
return;
}
mIsShowMenu = true;
int dx = (int)(mWidth * mMenuWeight);
mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
if(mListener != null){
mListener.onChanged(mIsShowMenu);
}
invalidate();
} public void hideMenu(){
if(!mIsShowMenu){
return;
}
mIsShowMenu = false;
int dx = (int)(mWidth * mMenuWeight);
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
if(mListener != null){
mListener.onChanged(mIsShowMenu);
}
invalidate();
} 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 * mMenuWeight / 2)){
if(dis > 0){
showMenu();
}else{
hideMenu();
}
}
break;
} return true;
} @Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(mWidth, mHeight);
int widthSpec = MeasureSpec.makeMeasureSpec((int)(mWidth * mMenuWeight), MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(mHeight, MeasureSpec.EXACTLY);
mMenuView.measure(widthSpec, heightSpec); widthSpec = MeasureSpec.makeMeasureSpec(mWidth, MeasureSpec.EXACTLY);
mPriView.measure(widthSpec, heightSpec);
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { mMenuView.layout(-(int)(mWidth * mMenuWeight), 0, 0, mHeight);
mPriView.layout(0, 0, mWidth, mHeight);
} public interface OnMenuChangedListener{
public void onChanged(boolean isShow);
} }
运行效果:
源代码下载:http://download.csdn.net/detail/lxq_xsyu/7231677
Android自定义组件系列【3】——自定义ViewGroup实现侧滑的更多相关文章
- Android自定义组件系列【4】——自定义ViewGroup实现双侧滑动
在上一篇文章<Android自定义组件系列[3]--自定义ViewGroup实现侧滑>中实现了仿Facebook和人人网的侧滑效果,这一篇我们将接着上一篇来实现双面滑动的效果. 1.布局示 ...
- Android自定义组件系列【6】——进阶实践(3)
上一篇<Android自定义组件系列[5]--进阶实践(2)>继续对任老师的<可下拉的PinnedHeaderExpandableListView的实现>进行了分析,这一篇计划 ...
- Android自定义组件系列【5】——进阶实践(2)
上一篇<Android自定义组件系列[5]--进阶实践(1)>中对任老师的<可下拉的PinnedHeaderExpandableListView的实现>前一部分进行了实现,这一 ...
- Android自定义组件系列【7】——进阶实践(4)
上一篇<Android自定义组件系列[6]--进阶实践(3)>中补充了关于Android中事件分发的过程知识,这一篇我们接着来分析任老师的<可下拉的PinnedHeaderExpan ...
- Android自定义组件系列【13】——Android自定义对话框如此简单
在我们的日常项目中很多地方会用到对话框,但是Android系统为我们提供的对话框样子和我们精心设计的界面很不协调,在这种情况下我们想很自由的定义对话框,或者有的时候我们的对话框是一个图片,没有标题和按 ...
- Android自定义组件系列【1】——自定义View及ViewGroup
View类是ViewGroup的父类,ViewGroup具有View的所有特性,ViewGroup主要用来充当View的容器,将其中的View作为自己孩子,并对其进行管理,当然孩子也可以是ViewGr ...
- Android自定义组件系列【15】——四个方向滑动的菜单实现
今天无意中实现了一个四个方向滑动的菜单,感觉挺好玩,滑动起来很顺手,既然已经做出来了就贴出来让大家也玩弄一下. 一.效果演示 (说明:目前没有安装Android模拟器,制作的动态图片太卡了,就贴一下静 ...
- Android自定义组件系列【12】——非UI线程绘图SurfaceView
一.SurfaceView的介绍 在前面我们已经会自定义View,使用canvas绘图,但是View的绘图机制存在一些缺陷. 1.View缺乏双缓冲机制. 2.程序必须重绘整个View上显示的图片,比 ...
- Android自定义组件系列【5】——进阶实践(1)
接下来几篇文章将对任老师的博文<可下拉的PinnedHeaderExpandableListView的实现>分步骤来详细实现,来学习一下大神的代码并记录一下. 原文出处:http://bl ...
随机推荐
- 洛谷 P1313 计算系数
题目描述 给定一个多项式(by+ax)^k,请求出多项式展开后x^n*y^m 项的系数. 输入输出格式 输入格式: 输入文件名为factor.in. 共一行,包含5 个整数,分别为 a ,b ,k , ...
- Shelled-out Commands In Golang
http://nathanleclaire.com/blog/2014/12/29/shelled-out-commands-in-golang/ Shelled-out Commands In Go ...
- BZOJ2142: 礼物(拓展lucas)
Description 一年一度的圣诞节快要来到了.每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物.不同的人物在小E 心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多.小E从商店 ...
- 2018-8-10 模拟赛T3(可持久化线段树)
出题人说:正解离线按DFS序排序线段维护区间和 但是对于树上每个点都有一个区间和一个值,两个点之间求1~m的区间和,这不就是用可持久化线段树吗. 只不过这个线段树需要区间修改,不过不需要标记下传,询问 ...
- 洛谷 P1303 A*B Problem
P1303 A*B Problem 题目描述 求两数的积. 输入输出格式 输入格式: 两行,两个数. 输出格式: 积 输入输出样例 输入样例#1: 复制 1 2 输出样例#1: 复制 2 说明 每个数 ...
- 阻止事件冒泡js jquery
jQuery之防止冒泡事件 冒泡事件就是点击子节点,会向上触发父节点.祖先节点的点击事件. 以下是html代码部分: <body> <div id="content&quo ...
- 使用knockout.js 完毕template binding
//1.template <script id="txn-details-template" type="text/html"> <!--St ...
- android之路Gallery 画廊
Gallery是一个内部元素能够水平滚动,而且能够把当前选择的子元素定位在它中心的布局组件. 我们还是直接看看样例的执行效果. watermark/2/text/aHR0cDovL2Jsb2cuY3N ...
- 挖一挖不经常使用到而又非常有用的重载-Split
Split这个基本上全部的程序开发者都用到,一般使用单字符和长字符串拆分字符串的较多.事实上另一个重载很好用.那就是多种组合字符来进行拆分. 比如: "aaaaaaaaaa{@}bbbbbb ...
- 安装orabbix
须知: (1). orabbix使用root用户安装. (2). orabbix安装在zabbix server端,而不是安装在Oracle端. 1.下载 Orabbix 2. 解压软件 un ...