关于自定义view 以及view中的动画实现
来源:互联网 发布:网络问卷有什么问题 编辑:程序博客网 时间:2024/05/22 17:22
最近研究了一下自定义view,现在记一下笔记:看的人不如自己去google developer自己学
自定义view用于定制特殊效果的view,或者在不愿意嵌套多个view(布局)时候实现复杂布局,
首先需要继承View类,或者如果功能比较接进已经有了的view,比如LinearLayout 可以直接继承它,然后实现自己功能;
继承之后,需要实现其构造方法,主要是用于建立内置的变量,可以在xml布局中进行设置的那种,这里掠过,另外,一些初始化也需要在这里做,主要是Paint 对象的初始化,最好不要在ondraw时候初始Paint对象,因为会造成卡顿问题;
通过重写 onMesure 方法,指定view的实际可用的大小,因为他的参数中给出了相应的宽度,也就是xml中设置的大小,需要通过MeasureSpec.getSize 取出,单位是px ,如果想要设置控件大小,需要用工具类将dp转化为px 然后进行设置
int h = MeasureSpec.getSize(heightMeasureSpec); int w = MeasureSpec.getSize(widthMeasureSpec);
获取大小是在onSizeChange 方法中获取的
//用于设定内容大小 ,因为这个参数会显示实际的大小 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh);// Log.e(TAG, "onSizeChanged: " + "w :" + w + " h:" + h); //获取到的是实际的宽高变化 //rect 是画矩形使用的东西 mRect = new Rect(0, 0, 150, 150); //画出来的不是实际大小,而在onmesure设置的是实际大小 mWidth = w; mHeight = h; mMipmapRect = new Rect(50, 50, 100, 100); }
然后主要的是ondraw 方法,用于绘制界面:主要能够绘制 圆 线 矩形 图像,
其中绘制图像还是比较繁琐一点的,需要借助工具类 Metrix 类对图像做出 位移 旋转 缩放等操作,还可以通过cavas 设置其颜色的渐近变化 和 多个图像之间的遮盖关系,这都是与图层相关的问题:
LinearGradient mshader=new LinearGradient(50,50,mWidth,mHeight,Color.RED,Color.BLUE, Shader.TileMode.CLAMP);//长度在sizechangge时候才能获取 mLinePaint.setShader(mshader);
这是颜色渐近变化,还可以使用repeat 镜像 等几种模式
图层遮盖使用 xformode 方法,具体没用过,可以用来实现圆角图片。。
通过rect 类加载图片可以实现图片的剪切。
主要说的是动画的两种实现方式:
主要都是通过ondraw 的不停重绘实现的,也就是调用 postInvalidate(),先要说一下对手势滑动的接收问题:先要重写ontouch方法,拿到回调,然后通过Detector 类进行动作的处理:
//需要从ontouchevent 中对滑动事件进行关联 @Override public boolean onTouchEvent(MotionEvent event) { boolean result = mDetector.onTouchEvent(event); return result; }
/* 集成手势感应的方式 */ class mListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { return true; } @Override public boolean onSingleTapUp(MotionEvent e) { return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {// mScroller.startScroll((int)e1.getX(),(int)e1.getY(),(int)e2.getX(),(int)e2.getY());// postInvalidate(); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { //模仿滑动的时候的滚动 mScroller.fling((int) e1.getX(), (int) e1.getY(), (int) velocityX/4, (int) velocityY/4, 0, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); //操作1 的方式是通过view的重绘实现的,因为onfling 的调用了好几次 所以重绘了好几次 然后就有了动画效果 if (Build.VERSION.SDK_INT >= 11) { //valueanimator 是对值的平滑过渡 mScrollAnimator.setDuration(mScroller.getDuration()); mScrollAnimator.start(); }else{ //第一种动画 postInvalidate();//滑动之后进行更新UI的操作 通过重绘进行操作的步骤 } return true; } }
这里onDown 方法必须实现,并且必须返回true,否则不能接受到任何回调
然后必须说一下Scroller 类,这个类是个工具类,具体作用,模拟滑动 拖动 或者fling 后不同时间的位置,通过getCurX 和getCurY 来得到一定时间的位置,用于模拟现实世界的变化,这是动画重要的一个类,尤其是在实现手势动作的时候。
第一种方式:通过ondraw 时候调用位置变化:
if(!mScroller.isFinished()){ mScroller.computeScrollOffset(); mRect.offsetTo(mScroller.getCurrX()-75,mScroller.getCurrY()-75); Log.e(TAG, "x is :"+mScroller.getCurrX() ); Log.e(TAG, "y is : "+mScroller.getCurrY() ); } if (!mScroller.isFinished()) { postInvalidate(); }
调用 mScroller.computeScrollOffset(); 会计算出时间内的一个位置 ,没结束就调用刷新继续计算,很直观
第二种方式:通过属性动画实现 ValueAnimator:
这个类主要作用是在一定时间内不停的进行回调,类似于一个闹钟,可以设置间隔和速度,然后调用方法,但是重绘仍然需要自己处理,它不做其他的事,只有值的变化:
在init 方法中:
mScrollAnimator=ValueAnimator.ofFloat(0,1); mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (!mScroller.isFinished()) { mScroller.computeScrollOffset(); //计算动画位置 mRect.offsetTo(mScroller.getCurrX()-75,mScroller.getCurrY()-75); postInvalidate(); } else { if (Build.VERSION.SDK_INT >= 11) { mScrollAnimator.cancel(); } } } });
这样就不停重绘,改变了位置,Scroller 类很重要,比如fling 这种操作,Scroller 会帮助计算出速度之后,算出某个时间的位置,以及结束之后滑动的总长度。
旋转是通过setRotate 实现的,不过会改变整个view的旋转。。旋转角度算法自己实现
public class TestView extends View { private Paint mPaint; private Paint mTextPaint; private Paint mLinePaint; private Paint bitmapPaint; private Bitmap mBitmap;//要画出来的bitmap对象 private int drawableWidth; private int drawbleHeight; private Paint circlePaint; private GestureDetector mDetector; //处理图形 Matrix mMatrix; String TAG = "view"; int mWidth; int mHeight; Rect mMipmapRect; private int angle = 0; //获取到滑动的数据 private Scroller mScroller; // 动画使用类 private ValueAnimator mScrollAnimator; /* 集成手势感应的方式 */ class mListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { return true; } @Override public boolean onSingleTapUp(MotionEvent e) { return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {// mScroller.startScroll((int)e1.getX(),(int)e1.getY(),(int)e2.getX(),(int)e2.getY());// postInvalidate(); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { //e1 起点的x y; e2 当前点的位置坐标信息 x y 都是坐标信息 float scrollTheta = vectorToScalarScroll( velocityX, velocityY, e2.getX() - mWidth / 2, e2.getY() - mHeight / 2);// Log.e(TAG, "e1 x :" + e1.getX());// Log.e(TAG, "e1 y :" + e1.getY());// Log.e(TAG, "e2 x :" + e2.getX());// Log.e(TAG, "e2 y :" + e2.getY());// Log.e(TAG, "velocity X :" + velocityX);// Log.e(TAG, "velocityY :" + velocityY); //模仿滑动的时候的滚动 mScroller.fling((int) e1.getX(), (int) e1.getY(), (int) velocityX/4, (int) velocityY/4, 0, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);// postInvalidate();//滑动之后进行更新UI的操作 通过重绘进行操作的步骤 //操作1 的方式是通过view的重绘实现的,因为onfling 的调用了好几次 所以重绘了好几次 然后就有了动画效果 if (Build.VERSION.SDK_INT >= 11) { //valueanimator 是对值的平滑过渡 mScrollAnimator.setDuration(mScroller.getDuration()); mScrollAnimator.start(); } return true; } } //需要从ontouchevent 中对滑动事件进行关联 @Override public boolean onTouchEvent(MotionEvent event) { boolean result = mDetector.onTouchEvent(event); return result; } public void setAngle(int angle) { this.angle = angle; invalidate(); requestLayout(); } public TestView(Context context) { super(context); init(); } public TestView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } //初始化paint private void init() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(getResources().getColor(android.R.color.holo_blue_bright)); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//设置抗锯齿的效果 假如我要我的文字的位置在哪 mTextPaint.setColor(getResources().getColor(android.R.color.black)); mTextPaint.setTextSize(DensityUtils.sp2px(getContext(), 12));// mTextPaint.setShader(); 设置颜色的渐进 //设置线条 颜色的渐变 渐变起点(像素 x,y)终点(x,y) 起始颜色 ,终点颜色,渐变模式 //clamp 渐变 mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);// mLinePaint.setColor(Color.RED);// mLinePaint.setShader(mshader); mLinePaint.setStyle(Paint.Style.STROKE); mLinePaint.setStrokeWidth(DensityUtils.dp2px(getContext(), 4)); //画图 bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.indicator_arrow);//拿到bitmap的对象 drawableWidth = mBitmap.getWidth(); drawbleHeight = mBitmap.getHeight(); bitmapPaint = new Paint(); mMatrix = new Matrix(); //画圆 circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); circlePaint.setStyle(Paint.Style.STROKE); mDetector = new GestureDetector(TestView.this.getContext(), new mListener()); //根据当前的版本创建Scroller 对象 if (Build.VERSION.SDK_INT < 11) { mScroller = new Scroller(getContext()); } else { mScroller = new Scroller(getContext(), null, true); } /** * 通过animation 动画实现 和通过 重绘进行实现的原理都是一样的 都调用了重绘 * 这个动画作用是只要不调用停止 mScrollAnimator.cancel(); 就会一直有回调 * 然后在回调里面不停改变绘制参数,对里面空间进行位置上面的重绘 * 并没有调用view对象之类的东西, 不比直接在ondraw里面直观,但是更合理 (官方说的) * duaration 就是不停的调用的时间 * */ mScrollAnimator=ValueAnimator.ofFloat(0,1); mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (!mScroller.isFinished()) { mScroller.computeScrollOffset(); //计算动画位置 mRect.offsetTo(mScroller.getCurrX()-75,mScroller.getCurrY()-75); postInvalidate(); } else { if (Build.VERSION.SDK_INT >= 11) { mScrollAnimator.cancel(); } } } }); } //实际画出来的大小是由 setMeasuredDimension 设定的 根据设定获取到设定的宽高,然后将其画出来 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int h = MeasureSpec.getSize(heightMeasureSpec); int w = MeasureSpec.getSize(widthMeasureSpec);// Log.e(TAG, "onMeasure: " + "with=" + w + " " + "height=" + h); //这样获取的就是像素点,也就是实际的像素大小,不是dp 需要将dp转化为px 然后运算// width=getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec); // 也可以这么设置宽高// height=getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec); setMeasuredDimension(w, h); //设置宽高, onsizechangge 时候显示出大小 } //最后执行的ondraw 不需要太在意效率 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); setRotation(30); canvas.drawRect(mRect, mPaint); /* 画出文字和直线 */// canvas.drawText("hello world", 50, 50, mTextPaint);// //设置渐变// LinearGradient mshader=new LinearGradient(50,50,mWidth,mHeight,Color.RED,Color.BLUE, Shader.TileMode.CLAMP);//长度在sizechangge时候才能获取// mLinePaint.setShader(mshader);// canvas.drawLine(50, 50, mWidth, mHeight, mLinePaint); /* 画出bitmap */// canvas.drawBitmap(mBitmap,0,0,bitmapPaint); //其实是剪切图形是时候比较有用// canvas.drawBitmap(mBitmap,new Rect(20,20,160,160),new Rect(40,40,150,150),bitmapPaint);// mMatrix.// mMatrix.setTranslate(mWidth / 2, mHeight / 2);//图片平移 确定位置//// //先旋转之后再从x 宽度上面进行拉伸两倍,高度不变// mMatrix.postScale(1, 2, mWidth / 2, mHeight / 2);// mMatrix.postRotate(angle, mWidth / 2, mHeight / 2);//图片根据角度1和初始位置进行旋转// canvas.drawBitmap(mBitmap, mMatrix, bitmapPaint);//// //画圆// canvas.drawCircle(mWidth/2,mHeight/2,DensityUtils.dp2px(getContext(),70),circlePaint);// tickScrollAnimation(); /**scroller 是个能够模拟滑动的类,根据滑动会给出一系列的值 然后进行界面重绘 * 通过scroller 类进行重绘 * 原理很直观,每次调用后重新得到scroller的位置 * */// if(!mScroller.isFinished()){// mScroller.computeScrollOffset();// mRect.offsetTo(mScroller.getCurrX()-75,mScroller.getCurrY()-75);// Log.e(TAG, "x is :"+mScroller.getCurrX() );// Log.e(TAG, "y is : "+mScroller.getCurrY() );// }// if (!mScroller.isFinished()) {// postInvalidate();// } /** * 通过动画操作运动 * 通过动画也能够实现,但是实现原理?? * 动画类会在设定的时间内不停的回调 然后就会执行重新绘制 */ }// private void tickScrollAnimation() {// if (!mScroller.isFinished()) {// mScroller.computeScrollOffset();// setPieRotation(mScroller.getCurrY());//// } else {// if (Build.VERSION.SDK_INT >= 11) {// mScrollAnimator.cancel();// }// onScrollFinished();//// }// } Rect mRect; //用于设定内容大小 ,因为这个参数会显示实际的大小 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh);// Log.e(TAG, "onSizeChanged: " + "w :" + w + " h:" + h); //获取到的是实际的宽高变化 //rect 是画矩形使用的东西 mRect = new Rect(0, 0, 150, 150); //画出来的不是实际大小,而在onmesure设置的是实际大小 mWidth = w; mHeight = h; mMipmapRect = new Rect(50, 50, 100, 100); } /** * 旋转的工具类 计算旋转位置 * Helper method for translating (x,y) scroll vectors into scalar rotation of the pie. * * @param dx The x component of the current scroll vector. * @param dy The y component of the current scroll vector. * @param x The x position of the current touch, relative to the pie center. * @param y The y position of the current touch, relative to the pie center. * @return The scalar representing the change in angular position for this scroll. */ private static float vectorToScalarScroll(float dx, float dy, float x, float y) { // get the length of the vector float l = (float) Math.sqrt(dx * dx + dy * dy); // decide if the scalar should be negative or positive by finding // the dot product of the vector perpendicular to (x,y). float crossX = -y; float crossY = x; float dot = (crossX * dx + crossY * dy); float sign = Math.signum(dot); return l * sign; }}
- 关于自定义view 以及view中的动画实现
- 自定义View中的动画
- View 动画的实现原理以及自定义实现
- Android:自定义view实现动画
- 自定义view+属性动画实现
- 关于自定义组合控件以及自定义view
- Android自定义View实现雷达扫描动画
- 自定义View——实现波浪动画
- Android自定义View实现雷达扫描动画
- 自定义View实现loading动画加载
- 自定义View:侧滑菜单动画实现
- Android 自定义View 实现loading动画
- Android 自定义View实现波浪动画
- 自定义View实现百度Loading动画
- Android自定义View的动画实现方法
- 自定义View实现百度Loading动画
- 自定义view实现打勾动画
- Android自定义View实现搜索动画效果
- Echerts循环动态打印series
- BZOJ1076 状态压缩DP 期望DP
- Spring事务机制—声明式事务
- Tomcat 工作原理
- BZOJ 1694 & 1742 [Usaco 2005 nov] 区间DP 解题报告
- 关于自定义view 以及view中的动画实现
- Go 语言——数据类型
- Springboot整合mybatis
- logcat命令补充(补充之前博文logcat抓取(崩溃、超时、异常、错误) )
- iOS开发:兼容适配iPhone X
- Android数据存储——SharedPreferences的使用
- 第三章-数字图像处理学习笔记
- C语言文件读写
- 使用httpclient 进行网络线程管理时,出现