有关自己定义ViewGroup的文章已经非常多了,我为什么写这篇文章,对于刚開始学习的人或者对自己定义组件比較生疏的朋友尽管能够拿来主义的用了,可是要一步一步的实现和了解当中的过程和原理才干真真脱离别人的代码,举一反三却不easy,非常多博主事实上不愿意一步一步的去写,这样非常耗时,可是假设能对读者有帮助,能从这篇文章中学会自己定义组件就达到我的目的了。

第一步:搭建框架来实现一个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实现側滑的更多相关文章

  1. Android自己定义组件系列【7】——进阶实践(4)

    上一篇<Android自己定义组件系列[6]--进阶实践(3)>中补充了关于Android中事件分发的过程知识.这一篇我们接着来分析任老师的<可下拉的PinnedHeaderExpa ...

  2. Android自己定义组件系列【6】——进阶实践(3)

    上一篇<Android自己定义组件系列[5]--进阶实践(2)>继续对任老师的<可下拉的PinnedHeaderExpandableListView的实现>进行了分析,这一篇计 ...

  3. Android自己定义组件系列【5】——进阶实践(2)

    上一篇<Android自己定义组件系列[5]--进阶实践(1)>中对任老师的<可下拉的PinnedHeaderExpandableListView的实现>前一部分进行了实现,这 ...

  4. Android自己定义组件系列【4】——自己定义ViewGroup实现双側滑动

    在上一篇文章<Android自己定义组件系列[3]--自己定义ViewGroup实现側滑>中实现了仿Facebook和人人网的側滑效果,这一篇我们将接着上一篇来实现双面滑动的效果. 1.布 ...

  5. Android自己定义组件系列【2】——Scroller类

    在上一篇中介绍了View类的scrollTo和scrollBy两个方法,对这两个方法不太了解的朋友能够先看<自己定义View及ViewGroup> scrollTo和scrollBy尽管实 ...

  6. Android自己定义组件系列【1】——自己定义View及ViewGroup

    View类是ViewGroup的父类,ViewGroup具有View的全部特性.ViewGroup主要用来充当View的容器.将当中的View作为自己孩子,并对其进行管理.当然孩子也能够是ViewGr ...

  7. Android自己定义组件系列【9】——Canvas绘制折线图

    有时候我们在项目中会遇到使用折线图等图形,Android的开源项目中为我们提供了非常多插件,可是非常多时候我们须要依据详细项目自己定义这些图表,这一篇文章我们一起来看看怎样在Android中使用Can ...

  8. Android自己定义组件系列【5】——高级实践(1)

    在接下来的几篇文章将任老师的博文<您可以下拉PinnedHeaderExpandableListView实现>骤来具体实现.来学习一下大神的代码并记录一下. 原文出处:http://blo ...

  9. Android自己定义组件系列【8】——面膜文字动画

    我们掩盖文字动画Flash中非经货共同体共同,由于Android应用程序开发人员做你想要做这个动画在应用程序中去?本文中,我们看的是如何自己的定义ImageView来实现让一张文字图片实现文字的遮罩闪 ...

随机推荐

  1. java基础56 HTML5的标签知识(网页知识)

    本文知识点(目录): 1.html常用标签    2.html实体标签    3.html媒体标签    4.html超链接标签    5.html图片标签    6.html标个标签 7.html框 ...

  2. 洛谷P2194HXY烧情侣

    传送门啦 这个题可以说是tarjan强连通分量的裸题,但需要维护每个强连通分量的最小值,所以做法就很明确了. 我们先明确几个数组的意思: 1.首先是tarjan缩点中的几个数组: dfn[i]:i点的 ...

  3. type Iterator does not take parameters

    在ubuntu编译java程序时报错:type Iterator does not take parameters 源码如下: package object; import java.util.*; ...

  4. POJ 2955 Brackets(括号匹配一)

    题目链接:http://poj.org/problem?id=2955 题目大意:给你一串字符串,求最大的括号匹配数. 解题思路: 设dp[i][j]是[i,j]的最大括号匹配对数. 则得到状态转移方 ...

  5. 20155225 2016-2017-2《Java程序设计》课程总结

    20155225 2016-2017-2<Java程序设计>课程总结 每周作业链接汇总 预备作业1:新的开始 预备作业2:C语言学习回顾 预备作业3:Linux基础入门和虚拟机的安装 第一 ...

  6. MEF实现设计上的“松耦合”(二)

    介绍了下MEF的基础用法,让我们对MEF有了一个抽象的认识.当然MEF的用法可能不限于此,比如MEF的目录服务.目录筛选.重组部件等高级应用在这里就不做过多讲解,因为博主觉得这些用法只有在某些特定的环 ...

  7. html meta标签使用总结(转)

    之前学习前端中,对meta标签的了解仅仅只是这一句. <meta charset="UTF-8"> 但是打开任意的网站,其head标签内都有一列的meta标签.比如我博 ...

  8. 【LOJ】#2057. 「TJOI / HEOI2016」游戏

    题解 我并不会做,我觉得很像网络流但是毫无建图思路 我猜了个贪心,写了一下--啥过了90分?!这数据是有多水啊.. 哦又是行列拆点 不过要按照'#'进行拆点,也就是一段横着的区间只能放一个炸弹,一段竖 ...

  9. Loadrunner参数化如何在记事本中将参数值显示超过100个参数值

    Loadrunner参数化如何在记事本中将参数值显示超过100个参数值 1.loadrunner的参数值没有最大的限度, 可以修改C:\Program Files\HP\LoadRunner\conf ...

  10. 洛谷 P3071 [USACO13JAN]座位Seating-线段树区间合并(判断找,只需要最大前缀和最大后缀)+分治+贪心

    P3071 [USACO13JAN]座位Seating 题目描述 To earn some extra money, the cows have opened a restaurant in thei ...