没有使用第三方类库,纯代码定制.主要用到的知识如下,
我们知道,不管是自定义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; } }}
版权声明:本文为博主原创文章,未经博主允许不得转载。