Canvas画布的使用
默认坐标:Canvas类相当于一个矩形画布,默认0,0坐标是左上角。用到的坐标都是画布上的(即视图坐标系)
其常用方法有,draw( )方法最后一个参数都为Paint 对象:
- drawRect(RectF rect,Paint paint);//在画布上绘制一个矩形;
- react 对象为描述需要在Canvas上区域;
参数一表示矩形左边的X坐标,余下三个同理。
- react 对象为描述需要在Canvas上区域;
save( );
//此方法用于保存当前画布的状态,一般多用于旋转画布的时候
restore();
///此方法用于恢复画布,即恢复到save( )方法存入时的状态,但在画布上画的东西不会被撤销。
rotate(float degrees, float px, float py);
//此方法用于旋转画布,参数一为旋转的角度(正数为顺时针方向转,负数为逆时针),参数二,三画布旋转的轴点. 当画布旋转时呢,其的坐标也是跟着一起转动了,所以就意味着一定要此Canvas的中心坐标点来旋转,不然转了后内容会丢失。当确定点后,是以此点的上直线为O度. 当转动到相应的角度后,又以那个角度会0度。
一般来说,一次转动 对应一个内容绘制。下次转动并不会把上次绘制的内容也转动了.
多用于圆盘类的自定义View,确定一个地方的内容就可以转动绘制,省去多次确定坐标.
RectF rectf =new RectF(float left,float top,float reight,float bottom);
drawPath(Path path,Paint paint);
//在画布上绘制一个Path图形(即图像的轮廓),图形复杂时就用这个。
- Path类常用方法用:
moveTo(float x,float y); //设置下次连接操作的坐标(移动的起点)。如不设置呢为Canvas的0,0 坐标。
lineTo(float x,float y);//设置上个点到此点用直线连接。
close();连接第一点到最后一个点,形成闭合。
更多内容查看此
drawBitmap(Bitmap bitmap,float left , float top , Paint paint) ;
//其实呢就是贴图。
因为一张图片长宽肯定是确定的,所以确定左上角的坐标就OK 了。
drawLine(float startx , float starty , float stopx , float stopy , Paint paint) ;
//画线,当x坐标的起始和终止的坐标不变时,变化的为Y时,画出来的时竖线。反之横线。
当x和y都是变的,画出来的就是斜线。
drawPoint(float x, float y, Paint paint);
//表示画点。
drawText(String text, float x, floaty, Paint paint) ;
//绘制文本,x,y坐标用于确定文字左上角的坐标。
drawOval(RectF oval, Paint paint);
//此为画椭圆,参数一用于确定椭圆外接矩形(用于确定椭圆能画多大).
drawCircle(float cx, float cy, float radius,Paint paint);
//此为画园,参数一二为确定圆的中心坐标。参数三为半径。
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) ;
//画弧,参数一用于确定弧能画多大,参数二为弧的起始角度,参数三为绘制的角度,(弧会顺时针绘制)。参数四为是否把所画弧的开始点结束点连接到弧心,为false弧线,true就封。
Paint 画笔的使用
Paint对象呢为用于在Canvas上画东西的笔;
常用方法:
setAntiAlias(boolean aa);
此用于用于设置是否开启抗锯齿,为true呢为开启,false为不开启。
setStyle(Paint.Style style);
此用于设置画笔的风格。默认为 FILL(填满) ;
STROKE表示笔为空心,仅仅画边。FILL_AND_STROKE表示中心部分为空,外围填满。
setStrokeWidth(float width);
此方法用于设置当画笔为STROKE时,延伸出去的宽度。
setStrokeCap(Paint.Cap cap);
此方法用于设置一般用于弧线,设置头部和尾部是圆角,默认为直角.
setColor(int color);
此方法用于设置画笔的颜色.
setTextSize(float textSize) ;
滑动移动距离的计算
@Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction( ) ) {
case MotionEvent.ACTION_DOWN:
lastx = (int) event.getX( );
break;
case MotionEvent.ACTION_MOVE:
movex = (int) event.getX()-lastx; //获取当前移动了多少距离;
break;
case MotionEvent.ACTION_UP:
//处理输入的离开的事件.
break;
}
return true;
}
如果在ACTION_MOVE中有重绘视图时,一定要最后重新获取当前的坐标,下次再执行到ACTION_MOVE状态才能正确计算偏移量,不然lastx一直是DOWN状态是的坐标.
X轴: 如果计算出移动的值为-值,那么就是向左滑,为正值,就是向右滑。
Y轴:如果计算出移动的值为-值,那么就是向上滑,为正值,就是向下滑。
自定义View 三大流程
通常要写自定义View呢
onMeasure( int widthMeasureSpec, int heightMeasureSpec) ; //测量;
onLayout(boolean changed, int left, int top, int right, int bottom) ; //布局;
onDraw(Canvas canvas);//绘制
onMeasure( )方法都得重写,因为系统默认对wrap_content的属性值处理是match_parent.所以想支持此属性就必须复写此方法来指定wrap_content的值。
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
setMeasuredDimension(measureHW(widthMeasureSpec,150) , measureHW(heightMeasureSpec,200));
}
private int widthMeasure(int measureSpec , int defultV) {
int result = defultV ;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
}else if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result , specSize);
}
return result;
}
其中使用到的MeasurSpec类,为帮助测量View的, onMeasure( )方法中传递进入的值,是一个32位的int 值.解析出这个值就能获得此View的模式 和 大小.一般用到的两种模式
EXACTLY:即精确模式,View 的layout_height 和layout_width属性指定为精确数字或者match_parent时,View使用的就是EXACTLY模式。
AT_MOST: 即最大值模式,当 View 的layout_height 和 layout_width 属性指定为wrap_content.就是此模式 意指 控件的大小随内容变化,但是不能超过父View
一般来说的话,不写ViewGroup的话onLayout是不用i复写的,没有包含其他View所以也就不用计算其子View 的布局坐标。
onDraw( ) ,此方法就是最终显示到屏幕上的内容. 在绘制自定义View时呢,记得一定要把其拆分开,然后一个个的绘制 .
自定义属性
首先values文件下,新建attrs.xml的xml文件。声明资源类型为子标签为
//其中
而 < attr > 元素表示的name属性表示View 控件的属性名,format属性表示支持的类型 .
要在布局文件中使用自定义属性呢,首先要要为这些自定义属性导入一个命名空间.
xmlns:app="http://schemas.android.com/apk/res-auto"
然后在具体的控件里,就能通过app:属性名 找到自定义属性了.
解析具体设置那些自定义属性,如下所示:
public TopBar(Context context, AttributeSet attrs) {
super(context, attrs);
getAttrs(attrs);
setView(context);
}
private void getAttrs(AttributeSet attrs) {
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.TopBar);
mTitleText = ta.getString(R.styleable.TopBar_title);
mTitleTextSize = ta.getDimension(R.styleable.TopBar_titleTextSize,10);
mTitleTextColor = ta.getColor(R.styleable.TopBar_titleTextColor,0);
mleftBackgound = ta.getDrawable(R.styleable.TopBar_leftBackground);
mleftColor = ta.getColor(R.styleable.TopBar_leftTextColor,0);
mleftText = ta.getString(R.styleable.TopBar_leftText);
mRightColor = ta.getColor(R.styleable.TopBar_rightTextColor,0);
mRightText = ta.getString(R.styleable.TopBar_rightText);
mRightBackgound = ta.getDrawable(R.styleable.TopBar_rightBackground);
ta.recycle();
}
public void setView(Context context) {
mleftButton = new Button(context);
mRightButton = new Button(context);
mTitle = new TextView(context);
mTitle.setText(mTitleText);
mTitle.setTextColor(mTitleTextColor);
mTitle.setTextSize(mTitleTextSize);
mTitle.setGravity(Gravity.CENTER);
mTitleLP = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.MATCH_PARENT);
mTitleLP.addRule(RelativeLayout.CENTER_IN_PARENT);
mTitle.setLayoutParams(mTitleLP);
mleftLP = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.MATCH_PARENT);
mleftLP.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
mleftButton.setText(mleftText);
mleftButton.setTextColor(mleftColor);
mleftButton.setBackground(mleftBackgound);
mleftButton.setLayoutParams(mleftLP);
mRightLp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.MATCH_PARENT);
mRightLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
mRightButton.setText(mRightText);
mRightButton.setTextColor(mRightColor);
mRightButton.setBackground(mRightBackgound);
mRightButton.setLayoutParams(mRightLp);
addView(mTitle);
addView(mRightButton);
addView(mleftButton);
setOnclik(mRightButton,mleftButton);
}
通过构造函数提供的AttributeSet类的对象,getContext().obtainStyledAttributes(AttributeSet set, int[] attrs); 把AttributeSet对象解析到自定义属性 容器里(即对应R.styleable.TopBar) ,获得一个TypedArray类的对象,调用getString(R.styleable.TopBar_title, 0 ) ,其他属性获取方式类似,参数一为索引名,传入自定义属性中的名字就好(一定要以-连接),参数二为没有获取到值时的默认值。获取到了之后呢就是到需要的对象处set 就好。
惯性滑动的处理
要处理惯性滑动呢,至少需要两个类 Scroller类对象和VelocityTracker类对象。其中VelocityTracker对象 为获取速度,Scroller 对象为滚动动画与移动.
Scroll 对象正常构造就好。VelocityTracker 对象呢 在 View 回调 onTouchEvent( ) 方法时,在其方法开始出调用abtion( ) 获得VelocityTracker 的实例。再调用VelocityTracker 的addMovement( MotionEvent event) 添加 移动事件 对象。
@Override
public boolean onTouchEvent(MotionEvent event) {
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain(); //创建速度追踪的对象。
}
velocityTracker.addMovement(event); //添加移动事件的对象。
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastx = (int)event.getX();
break;
case MotionEvent.ACTION_MOVE:
movex = (int) event.getX()-lastx;
break;
case MotionEvent.ACTION_UP:
velocityTracker.computeCurrentVelocity(1000);//设置计量单位,毫秒. 就是多少毫秒收集一次移动的像素。
float currentVelocityx = velocityTracker.getXVelocity(); //分别获取X ,Y 轴的速度
float currentVelocityy = velocityTracker.getYVelocity();
if ( Math.abs(currentVelocityx) < 800) { //设置不随便一滑就滚动了。
return true;
}
//此处把X的距离取反是为了方便统一。fling()方法就是惯性滑动的处理和动画。
scroller.fling(130,starty,(int)(-Math.abs(currentVelocityx)),(int)(Math.abs(currentVelocityy)),0,1080,0,1920);
velocityTracker.recycle(); //回收对象
velocityTracker = null ;
break;
}
return true;
}
@Override
public void computeScroll() { //计算滚动的距离
if (scroller.computeScrollOffset()) {
int currX = scroller.getCurrX(); // 获得当前X轴 的滚动偏移量。
invalidate(); //没有滚动完,所以还得调用重绘函数.
}
}
scroller.fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) ,此方法是关键。 startScroll() 对应的是平滑移动,而fling( ) 方法对应得就是 急速滚动. 需要注意的就是 起始的 X,Y轴位置 是不能小于 参数六 和 参数七 对应的最小滑动到的位置,不然滚动的动画效果不会触发,只是滚动了。 其中呢computeScroll( )方法是在onDraw( )中回调的 .需要的是当使用的是startScroll( )方法呢,其后一定要调用invalidate() (重绘) . 而fling( )方法不需要 ,可能其方法内部已经调用了。 computeScrollOffset( ) 用于判断整个滚动事件是否已经结束,true 表示没有,false就是滚动完了.