独角兽企业重金招聘Python工程师标准>>>
近在系统学习自定义View这一块的知识,前面几篇基本都是理论知识,这篇博客着重从实战来加强对自定义View的理解与运用。实现的两种效果,分别代表自定义View与自定义ViewGroup。
效果图:
上面的是一个可以滑动的刻度尺,支持快速滑动,选择的数字也会显示在下方;下面的是一个经典的流式布局,会根据文字长度自动进行布局。一起看看怎么实现的吧:
一.准备工作
1.布局文件
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:myscroll="/apk/res-auto"android:id="@+id/activity_five"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/white"android:orientation="vertical"><org.tyk.android.artstudy.MySelectViewandroid:id="@+id/my_selectview"android:layout_width="match_parent"android:layout_height="50dp"android:layout_marginTop="50dp"myscroll:lineColor="@color/font_text"myscroll:textColor="@color/strong"myscroll:textSize="20dp"></org.tyk.android.artstudy.MySelectView><TextViewandroid:id="@+id/number_txt"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="5dp"android:layout_marginTop="20dp"android:text="选择的数字为:"android:textSize="20dp" /><org.tyk.android.artstudy.MyFlowLayoutandroid:id="@+id/my_flowlayout"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="50dp"android:background="@color/bg_page"></org.tyk.android.artstudy.MyFlowLayout></LinearLayout>
从上到下的线性布局,依次是滑动刻度尺,数字TextView,流式布局,以及设置了一些自定义的属性。
2.自定义滑动刻度尺的初始准备
public MySelectView(Context context) {this(context, null);}public MySelectView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MySelectView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//获取我们自定义的样式属性TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MySelectView, defStyleAttr, 0);int n = array.getIndexCount();for (int i = 0; i < n; i++) {int attr = array.getIndex(i);switch (attr) {case R.styleable.MySelectView_lineColor:// 默认颜色设置为黑色lineColor = array.getColor(attr, Color.BLACK);break;case R.styleable.MySelectView_textColor:textColor = array.getColor(attr, Color.BLACK);break;case R.styleable.MySelectView_textSize:// 默认设置为16sp,TypeValue也可以把sp转化为pxtextSize = array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(PLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));break;}}array.recycle();init();}public void init() {mPaint = new Paint();mPaint.setAntiAlias(true);bigBound = new Rect();smallBound = new Rect();}public void setmStartWidth(int mStartWidth) {this.mStartWidth = mStartWidth;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width;int height;int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);if (widthMode == MeasureSpec.EXACTLY) {width = widthSize;} else {width = widthSize * 1 / 2;}if (heightMode == MeasureSpec.EXACTLY) {height = heightSize;} else {height = heightSize * 1 / 2;}setMeasuredDimension(width, height);}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);mWidth = getWidth();mHeight = getHeight();mStartWidth = 0;}
初始化自定义控件,获取自定义控件的样式属性,初始化相关工具,重写onMeasure()测量自定义控件大小,重写onLayout()获取自定义控件宽高。
二.自定义滑动刻度尺的实现
1.重写onDraw()方法绘制刻度尺
@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);mPaint.setColor(lineColor);//画背景canvas.drawLine(0, 0, mWidth, 0, mPaint);canvas.drawLine(0, mHeight, mWidth, mHeight, mPaint);//画数字for (int i = 0; i < 1000; i++) {if (i % 5 == 0) {mPaint.setColor(textColor);canvas.drawLine(mStartWidth, 0, mStartWidth, getHeight() / 3, mPaint);mPaint.setTextSize(textSize);mPaint.getTextBounds(String.valueOf(i), 0, String.valueOf(i).length(), bigBound);canvas.drawText(String.valueOf(i), mStartWidth - bigBound.width() / 2, getHeight() / 2 + bigBound.height() * 3 / 4, mPaint);} else {mPaint.setColor(lineColor);mPaint.setTextSize(textSize - 15);canvas.drawLine(mStartWidth, 0, mStartWidth, getHeight() / 5, mPaint);mPaint.getTextBounds(String.valueOf(i), 0, String.valueOf(i).length(), smallBound);canvas.drawText(String.valueOf(i), mStartWidth - smallBound.width() / 2, getHeight() / 2 + smallBound.height() * 3 / 4, mPaint);}mStartWidth += mWidth / 10;}//画中间刻度线mPaint.setColor(textColor);canvas.drawLine(mWidth / 2, 0, mWidth / 2, getHeight() / 3, mPaint);}
绘制背景的两条实线,绘制中间的数字,绘制中间的刻度线。
2.重写onTouchEvent()方法处理滑动事件
@Overridepublic boolean onTouchEvent(MotionEvent event) {if (velocityTracker == null) {velocityTracker = VelocityTracker.obtain();}velocityTracker.addMovement(event);int x = (int) event.getX();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:xDown = x;break;case MotionEvent.ACTION_MOVE:xMove = x;mStartWidth = xScroll + (xMove - xDown);invalidate();int numberScroll = (int) Math.round(Double.valueOf(mStartWidth) / Double.valueOf(mWidth / 10));listener.getNumber(Math.abs(numberScroll - 5));break;case MotionEvent.ACTION_UP:xUp = x;xScroll = xScroll + (xUp - xDown);//处理快速滑动puteCurrentVelocity(1000);int scrollX = (int) velocityTracker.getXVelocity();xScroll = xScroll + scrollX;ValueAnimator walkAnimator = ValueAnimator.ofInt(mStartWidth, xScroll);walkAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mStartWidth = (int) animation.getAnimatedValue();invalidate();}});walkAnimator.setDuration(500);walkAnimator.start();walkAnimator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {//处理惯性滑动int endX = xScroll % (mWidth / 10);if (Math.abs(endX) < mWidth / 20) {xScroll = xScroll - endX;mStartWidth = xScroll;invalidate();} else {xScroll = xScroll + (Math.abs(endX) - mWidth / 10);mStartWidth = xScroll;invalidate();}}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}});int number = (int) Math.round(Double.valueOf(xScroll) / Double.valueOf(mWidth / 10));listener.getNumber(Math.abs(number - 5));break;}return true;}
这一块是整个自定义滑动刻度尺的重点,慢慢分析一下:
初始化VelocityTracker,并且把要追踪的MotionEvent注册到VelocityTracker的监听中,用来跟踪触摸屏事件,主要用来处理滑动刻度尺的快速滑动。
MotionEvent.ACTION_DOWN: 获取水平方向X的坐标
MotionEvent.ACTION_MOVE: 获取水平方向滑动的距离,然后不断改变绘制的开始位置,再调用invalidate()来进行重绘,达到滑动的效果。后面两句代码是为了让下面显示的数字能够实时更新,接口回调。
MotionEvent.ACTION_UP:
1.获取滑动到总距离。
2.处理快速滑动,首先获取1秒内X方向所滑动像素值,然后确定最终滑动的位置。通过一个属性动画,不断改变绘制的开始位置,再调用invalidate()来进行重绘,达到快速滑动的效果。
3.处理惯性滑动,仔细查看效果图你会发现,当最后滑动的终点位置不足一半时,会自动滑动到前一个位置;当最后滑动的终点位置超过一半时,会自动滑动到下一个位置。这里其实就是在动画结束的时候,进行判断,然后调用invalidate()来进行重绘,达到惯性滑动的效果。
4.最后两句代码是为了让下面显示的数字最终能够实时更新,接口回调。
三.自定义流式布局的实现
@Overridepublic ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {return new MarginLayoutParams(getContext(), attrs);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);// 计算出所有的childView的宽和高for (int i = 0; i < getChildCount(); i++) {View childView = getChildAt(i);measureChild(childView, widthMeasureSpec, heightMeasureSpec);}setMeasuredDimension(sizeWidth, sizeHeight);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {int left = 0;int top = 0;for (int i = 0; i < getChildCount(); i++) {View childView = getChildAt(i);MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();int lc = left + params.leftMargin;int rc = childView.getMeasuredWidth() + lc;int tc = top + params.topMargin;int bc = childView.getMeasuredHeight() + tc;childView.layout(lc, tc, rc, bc);//超过宽度则换行if (rc + childView.getMeasuredWidth() > getMeasuredWidth()) {left = 0;top = bc;} else {left = rc;}}}
1.重写generateLayoutParams()方法返回MarginLayoutParams的实例,使自定义的流式布局能够支持margin属性
2.重写onDraw()方法计算出所有的childView的宽和高以及测量模式,并且设置自己的宽高
3.重写onLayout()方法对所有childView进行定位(设置childView的绘制区域),并且根据childView的宽度进行自动换行。
具体使用:
public void init() {stringList.add("数据库");stringList.add("移动开发");stringList.add("前端开发");stringList.add("微信小程序");stringList.add("服务器开发");stringList.add("PHP");stringList.add("人工智能");stringList.add("大数据");mySelectView = (MySelectView) findViewById(R.id.my_selectview);myFlowLayout = (MyFlowLayout) findViewById(R.id.my_flowlayout);for (String textView : stringList) {LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT);params.setMargins(40, 40, 40, 40);TextView showText = new TextView(this);showText.setLayoutParams(params);showText.setTextColor(getResources().getColor(R.color.text_color));showText.setTextSize(20);showText.setText(textView);showText.setBackground(getResources().getDrawable(R.drawable.flag_01));myFlowLayout.addView(showText);}numberTxt = (TextView) findViewById(R.id.number_txt);mySelectView.setListener(this);}
将需要设置的文字动态添加到我们的流式布局中去即可,自定义的流式布局会自动根据添加文字的大小进行布局,达到最后的效果。
源码地址:
/18722527635/AndroidArtStudy