没有使用第三方类库,纯代码定制.主要用到的知识如下,
我们知道,不管是自定义View还是系统提供的TextView这些,它们都必须放置在LinearLayout等一些ViewGroup中,因此理论上我们可以很好的理解onMeasure(),onLayout(),onDraw()这三个函数:1.View本身大小多少,这由onMeasure()决定;2.View在ViewGroup中的位置如何,这由onLayout()决定;3.绘制View,onDraw()定义了如何绘制这个View。
一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。它有三种模式:UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;AT_MOST(至多),子元素至多达到指定大小的值。
它常用的三个函数:
1.static int getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一)
2.static int getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)
3.static int makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式)
public static final int ACTION_DOWN = 0; // 按下事件
public static final int ACTION_UP = 1; // 抬起事件
public static final int ACTION_MOVE = 2; // 手势移动事件
public static final int ACTION_CANCEL = 3; // 取消
还有触摸事件,滚动的实现等,代码注释很详细
public class MainUI extends RelativeLayout {
private Context context;
private FrameLayout middleMenu,leftMenu,rightMenu;
//设置模版
private FrameLayout middlemask;
private Scroller scroller;
//属于自定义view,这里就需要用到这两个构造函数
public MainUI(Context context){
super(context);
inintView(context);
}
public MainUI(Context context, AttributeSet attrs) {
super(context, attrs);
inintView(context);
}
//初始化各种变量
public void inintView(Context context){
this.context = context;
//然后我们需要三个界面布局,这里用framelayout来承载
//设置滑动的是当前content和滑动样式
scroller = new Scroller(context,new DecelerateInterpolator());
leftMenu = new FrameLayout(context);
middleMenu = new FrameLayout(context);
rightMenu = new FrameLayout(context);
middlemask = new FrameLayout(context);
//为了区分给每个部分设置颜色
leftMenu.setBackgroundColor(Color.RED);
middleMenu.setBackgroundColor(Color.BLUE);
rightMenu.setBackgroundColor(Color.GREEN);
middlemask.setBackgroundColor(Color.GRAY);
middlemask.setAlpha(0);
//添加到当前布局
addView(leftMenu);
addView(middleMenu);
addView(rightMenu);
addView(middlemask);
//接下来要测量宽度,好用来给这三个部分分别设置宽度,放在onmeasure中
}
/**这个方法决定view本身的大小 * 这里的两个参数分别是屏幕的宽和高 * @param widthMeasureSpec * @param heightMeasureSpec */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//中间的宽度正好是整个屏幕
middleMenu.measure(widthMeasureSpec, heightMeasureSpec);
middlemask.measure(widthMeasureSpec, heightMeasureSpec);
//旁边的则最多为屏幕的80%
int realWidth = MeasureSpec.getSize(widthMeasureSpec);
int tempWidthMeasure = MeasureSpec.makeMeasureSpec(
(int)(realWidth*0.8f),MeasureSpec.EXACTLY);
leftMenu.measure(tempWidthMeasure, heightMeasureSpec);
rightMenu.measure(tempWidthMeasure, heightMeasureSpec);
}
/**这个方法决定view在layout中的位置 *四个参数对应屏幕 左上右下 * @param changed */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//中间菜单是中间的屏幕
middleMenu.layout(l, t, r, b);
middlemask.layout(l, t, r, b);
//左边的菜单则左边界要扩充
leftMenu.layout(l - leftMenu.getMeasuredWidth(), t, r, b);
//右边界左右都要设置
rightMenu.layout(
l + middleMenu.getMeasuredWidth(),
t,
l + middleMenu.getMeasuredWidth()
+ rightMenu.getMeasuredWidth(), b);
//接下来添加滑动事件
}
private boolean isTsetCompete;
/** * 处理相应的触摸事件 * @param ev * @return */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!isTsetCompete){
getEventType(ev);
return true;
}
//如果是左右滑动
if (isleftrightmove){
switch(ev.getActionMasked()){
case MotionEvent.ACTION_MOVE:
//得到滑动距离
int currScrollX = getScrollX();
//得到移动距离
int dis_x = (int)(ev.getX() -point.x);
//他俩差值肯定在20之间
int expectX = -dis_x +currScrollX;
int finalx=0;
if (expectX<0){
//右滑距离
finalx = Math.max(expectX,-leftMenu.getMeasuredWidth());
}else{
//左滑距离
finalx = Math.min(expectX,rightMenu.getMeasuredWidth());
}
scrollTo(finalx,0);
point.x = (int) ev.getX();
break;
//下面判断继续滑动或者手指离开屏幕
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//判断如果滑动距离大于一半则自动滑动出来,否则滑动回去
currScrollX = getScrollX();
if (Math.abs(currScrollX) > leftMenu.getMeasuredWidth() >>1) {
if (currScrollX < 0) {
scroller.startScroll(currScrollX, 0, -leftMenu.getMeasuredWidth() - currScrollX, 0);
}else {
scroller.startScroll(currScrollX,0,leftMenu.getMeasuredWidth()-currScrollX,0);
}
}else{
scroller.startScroll(currScrollX,0,-currScrollX,0);
}
//用于屏幕刷新
invalidate();
isleftrightmove = false;
isTsetCompete = false;
break;
}
}
return super.dispatchTouchEvent(ev);
}
/** * 在滑动改变距离的同时改变透明度,这种方式很好,因为会先调用父类的方法,所以不会影响到原来程序的运行 * @param x * @param y */
@Override
public void scrollTo(int x, int y) {
super.scrollTo(x, y);
int cruX = Math.abs(getScrollX());
float fo = cruX / (float)middleMenu.getMeasuredWidth();
middlemask.setAlpha(fo);
}
/** * 滚动条的回调方法 */
@Override
public void computeScroll() {
super.computeScroll();
if (!scroller.computeScrollOffset()){
return;
}
int tempX = scroller.getCurrX();
scrollTo(tempX,0);
}
private Point point = new Point();
private static final int TSET_DIS = 20;
private boolean isleftrightmove;
/** * 用于判断触摸事件类型的函数 * @param ev */
private void getEventType(MotionEvent ev) {
switch(ev.getActionMasked()){
case MotionEvent.ACTION_DOWN:
//得到当前坐标
point.x = (int) ev.getX();
point.y = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int dX = Math.abs((int)ev.getX()-point.x);
int dY = Math.abs((int)ev.getY()-point.y);
//左右滑动
if (dX >=TSET_DIS && dX>dY){
isleftrightmove = true;
isTsetCompete = true;
//为了滑动后可以再次滑动
point.x = (int) ev.getX();
point.y = (int) ev.getY();
}else if (dY>=TSET_DIS&&dY>dX){
isleftrightmove = false;
isTsetCompete = false;
point.x = (int) ev.getX();
point.y = (int) ev.getY();
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
break;
}
}
}