美高梅网址注册-澳门mgm4858集团登录网址
做最好的网站
来自 澳门mgm4858集团登录网址 2019-09-30 15:41 的文章
当前位置: 美高梅网址注册 > 澳门mgm4858集团登录网址 > 正文

三、自定义实现主要过程,在实现效果的基础上

加载动画效果图

图片 1loadingView.gif

让View支持padding属性

修改onDraw()方法如下:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        final int paddingLeft=getPaddingLeft();
        final int paddingRight=getPaddingRight();
        final int paddingTop=getPaddingTop();
        final int paddingBottom=getPaddingBottom();
        int width = getWidth()-(paddingLeft+paddingRight);
        int height = getHeight()-(paddingTop+paddingBottom);
       canvas.translate(width/2,height/2);
        canvas.drawCircle(0,0,100,mPaint);
    }

 <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
        <com.example.ahuang.viewandgroup.View.BasicCustomView
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:background="#111fff"
            android:paddingLeft="30dp"
            android:paddingTop="30dp"/>
    </LinearLayout>

图片 2

我们看到已经支持padding属性了.

代码下载 https://github.com/baojie0327/ViewAndGroup

childView.layout(mPainterPosX + space, mPainterPosY, mPainterPosX + width + space, mPainterPosY + height);执行ChildView的绘制。

项目Github地址:

在实现效果的基础上,我们尽可能的也要考虑到性能优化。例如,上述效果中,在控件的setVisibility方法调用后,我们移除了控件中的View并将其本身从父布局移除,并且定义标志位isStopAnimation判断是否还要继续执行动画。具体详见代码中实现。

写在前面

Android已经为我们提供了丰富的组件库,让我们可以实现各种UI效果。但是如果如此众多的组件还不能满足我们的需求,怎么办呢?别急,android组件也为我们提供了非常方便的拓展方法,通过对现有系统组件的继承,可以方便地实习那我们自己的功能。
自定义View作为Android的一项重要技能,一直以来被初学者认为是代表高手的象征,这篇文章就带大家了解下自定义View的过程。

通过读TabLayout源码,我们发现思路1,直接继承Tablayout重写onLayout和onMeasure对子view重写排列和测量不行,因为TabLayout里是加了一个私有的SlidingTabStrip作为子类,Item的view都是加在SlidingTabStrip中,没有办法通过重写私有SlidingTabStrip的onLayout和onMeasure。

接下来,我们就一步一步的实现这个效果

  1. 自定义轮询改变形状的ShapeChangeView

    • 实现比较简单,主要是图形的轮询绘制
    public class ShapeChangeView extends View { //初始形状 public Shape mCurShape = Shape.CIRCLE; private Paint mPaint; private Path mPath; /** * 定义形状的枚举类型 */ public enum Shape { CIRCLE, RECTANGLE, TRIANGLE, } public ShapeChangeView(Context context) { this(context, null); } public ShapeChangeView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public ShapeChangeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mPath = new Path(); mPaint = new Paint(); mPaint.setAntiAlias; mPaint.setStyle(Paint.Style.FILL); } /** * 获取当前View绘制的形状 * @return */ public Shape getCurShape() { return mCurShape; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //指定View的宽高 int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(width>height?height:width, width>height?height:width); } @Override protected void onDraw(Canvas canvas) { super.onDraw; int center = getWidth() / 2; switch (mCurShape) { case CIRCLE: //画圆 mPaint.setColor(ContextCompat.getColor(getContext(),R.color.circle_color)); canvas.drawCircle(center, center, center, mPaint); break; case RECTANGLE: //画正方形 mPaint.setColor(ContextCompat.getColor(getContext(),R.color.rect_color)); canvas.drawRect(0, 0, getRight(), getBottom(), mPaint); break; case TRIANGLE: //用Path画三角形 mPaint.setColor(ContextCompat.getColor(getContext(),R.color.triangle_color)); //指定path的起点 mPath.moveTo(getWidth; mPath.lineTo(0,  (getWidth() / 2 * Math.sqrt; mPath.lineTo(getWidth(),  (getWidth() / 2 * Math.sqrt; canvas.drawPath(mPath, mPaint); break; } } /** *轮询改变当前View绘制的形状 */ public void changeShape() { switch (mCurShape) { case CIRCLE: mCurShape = Shape.RECTANGLE; break; case RECTANGLE: mCurShape = Shape.TRIANGLE; break; case TRIANGLE: mCurShape = Shape.CIRCLE; break; } invalidate(); }
    
  2. 定义LoadingView组合控件的布局R.layout.layout_loading_view,摆放其三部分控件

    <LinearLayout xmlns:andro android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <com.m1Ku.progressview.view.view4.ShapeChangeView android: android:layout_width="30dp" android:layout_height="30dp" android:layout_marginBottom="90dp" /> <View android: android:layout_width="33dp" android:layout_height="6dp" android:background="@drawable/loading_shadow" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:text="@string/on_loading" /></LinearLayout>
    
  3. 继承LinearLayout定义组合控件LoadingView,加载布局并开启动画

    /** *加载布局,初始化控件 */private void initLayout() { inflate(getContext(), R.layout.layout_loading_view, this); mShapeChangeView = findViewById(R.id.shapeChangeView); mShadowView = findViewById(R.id.shadowView); //开启动画 post(new Runnable() { @Override public void run() { startFallAnimation; } /** * 定义 下落动画 和 阴影缩小动画 */ private void startFallAnimation() { if (isStopAnimation) { return; } //下落的动画 ObjectAnimator translateAnimation = ObjectAnimator.ofFloat(mShapeChangeView, "translationY", 0, translateDistance); translateAnimation.setInterpolator(new AccelerateInterpolator; //阴影缩小动画 ObjectAnimator scaleAnimation = ObjectAnimator.ofFloat(mShadowView, "scaleX", 1f, 0.4f); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(translateAnimation, scaleAnimation); animatorSet.setDuration(animateTime); animatorSet.setInterpolator(new AccelerateInterpolator; animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mShapeChangeView.changeShape(); startUpAnimation; animatorSet.start(); } /** * 定义 弹起动画 和 阴影放大动画 */ private void startUpAnimation() { if (isStopAnimation) { return; } //弹起的动画 ObjectAnimator translateAnimation = ObjectAnimator.ofFloat(mShapeChangeView, "translationY", translateDistance, 0); //阴影放大的动画 ObjectAnimator scaleAnimation = ObjectAnimator.ofFloat(mShadowView, "scaleX", 0.4f, 1f); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(translateAnimation, scaleAnimation); animatorSet.setDuration(animateTime); animatorSet.setInterpolator(new DecelerateInterpolator; animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); //弹起时旋转 startRotateAnimation(); } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); startFallAnimation; animatorSet.start(); } //旋转动画 private void startRotateAnimation() { if (isStopAnimation) { return; } ObjectAnimator rotateAnimation = null; switch (mShapeChangeView.getCurShape { case CIRCLE: case RECTANGLE: rotateAnimation = ObjectAnimator.ofFloat(mShapeChangeView, "rotation", 0, 180); break; case TRIANGLE: rotateAnimation = ObjectAnimator.ofFloat(mShapeChangeView, "rotation", 0, -120); break; } rotateAnimation.setDuration(animateTime); rotateAnimation.start(); }
    
自定义View的注意事项
  • 让View支持wrap_content
    这是因为直接继承View或ViewGroup的控件,如果不在onMeasure中处理wrap_content,那么外界在布局中使用wrap_content时就无法达到预期效果
  • 让View支持padding
    直接继承View的控件,如果不再draw方法中处理padding,那么这个属性是无法起作用的。直接继承ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致pading和子元素的margin失效
  • 不要在View中使用Handler
    这是因为View内部本身就提供了post系列方法,完全可以替代Handler的作用。除非你很明确要用Handler来发送消息。
  • View中如果有线程和动画,及时停止
    如果有线程和动画需要停止的时候,onDetachedFromWindow就恶意做到。这是因为当包含此View的Activity退出或者当前View被remove时,View的onDetachedFromWindow方法就会被调用。相对的,当包含此View的Activity启动时onAttachedToWindow会被调用。同时,View不可见时,我们也需要停止线程和动画,如果不及时停止,可能会导致内存泄漏。
  • 如果有滑动嵌套时,当然要处理好滑动冲突的问题。

TabLayout前两个个构造函数都是调用了自己的第三个构造函数,第三个构造函数里面添加了一个私有内部类SlidingTabStrip作为子view。

Tips

通常有以下三种方式来实现自定义View

  1. 重写View来实现全新的控件
  2. 对现有View进行扩展
  3. 通过组合来实现新的控件

我们前面的三篇效果都是用第一种重写View来实现的,而本篇效果则是结合1&3来实现的组合控件

让View支持wrap_content

之所以不支持wrap_content属性,是因为我们的自定义View没有重写onMeasure()方法,View默认的onMeasure()方法只支持EXACTLY模式,所以可以指定控件的具体宽高值或者match_parent属性,如果要自定义的view支持wrap_content属性,就必须重写onMeasure()方法。
加入代码如下:

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      //  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
    }


    /**
     * 获得测量的宽度
     * @param widthMeasureSpec
     * @return
     */
    private int measureWidth(int widthMeasureSpec){
        int width = 0;
        int mode=MeasureSpec.getMode(widthMeasureSpec); //获得测量模式
        int size=MeasureSpec.getSize(widthMeasureSpec); //获得测量值
        if (mode==MeasureSpec.EXACTLY){ //精准测量模式
            width=size;
        }else {
            width=300;
            if (mode==MeasureSpec.AT_MOST){
                width=Math.min(width,size);
            }
        }
        return  width;
    }


    /**
     * 获得测量的高度
     * @param heightMeasureSpec
     * @return
     */
    private int measureHeight(int heightMeasureSpec){
        int height = 0;
        int mode=MeasureSpec.getMode(heightMeasureSpec); //获得测量模式
        int size=MeasureSpec.getSize(heightMeasureSpec); //获得测量值
        if (mode==MeasureSpec.EXACTLY){ //精准测量模式
            height=size;
        }else {
            height=300;
            if (mode==MeasureSpec.AT_MOST){
                height=Math.min(width,size);
            }
        }
        return  height;
    }

图片 3

可以看到,重写onMeasure()方法后,VIew已经支持wrap_content了。

图片 4

效果实现分析

效果图中控件摆放是从上到下的,我们自然想到组合控件继承LinearLayout实现。而控件主要由图中的三部分组成

  1. 第一部分:自定义一个可以轮询改变形状的View叫ShapeChangeView,对外提供变形的方法
  2. 第二部分:它作为第一部分的阴影展示,我们放一个椭圆形的View(ShadowView)即可
  3. 第三部分:放在控件最底层的TextView
  4. 分析动画效果:
    • ShapeChangeView是上下的位移动画,而ShadowView是大小的缩放效果,这两个动画是同时执行的
    • 两者关系是:当ShapeChangeView下落时,ShadowView放大;ShapeChangeView弹起时,ShadowView缩小
    • 监听ShapeChangeView下落结束时,调用其改变形状的方法,弹起时让其改变形状
    • 最后,在弹起和下落动画的结束时相互调用,便形成这种动画效果
注意事项

在自定义View中,通常有下列比较重要的方法:

  • onFinishInflate():从xml中加载组件后调用
  • onSizeChanged():当组件的大小发生变化时调用
  • onMeasure():测量组件时调用,是View支持wrap_content属性
  • onLayout():确定组件显示位置时调用
    -onTouchEvent():界面上有触摸事件时调用
    当然,创建自定义View的时候,不一定要全部重写上述方法,只需按照需要重写即可。
    通常,有以下三种方法实现自定义View
  • 对现有控件进行扩展
  • 通过组合实现新的控件
  • 重写View实现全新控件

b、既然网上没有,就只能自己实现了。实现自定义View一般有4种思路。

这是早期的58同城的加载动画效果。说到加载动画效果,一般会用帧动画或者属性动画来实现,像京东和饿了么有一个小人一直在跑是用帧动画实现的,而我们今天写的这个则是用属性动画来实现。

自定义View的分类
  • 继承 View重写 onDraw 方法
    这种方式主要用于显示不规则的效果哦,即这种效果不方便用布局组合来实现,往往需要静态或者动态的显示一些不规则的图形采用这种方式需要自己支持wrap_content,并且padding也需要自己处理。
  • 继承ViewGroup派生特殊的Layout
    这种方法主要用于实现自定义布局,即除了LinearLayout,RelativeLayout等系统布局之外的一种重新定义的全新的布局,当某种效果很像
    几种View组合在一起的时候就可以采用这种方法。
    这种方法稍微复杂一些,需要合适的处理ViewGroup的测量和布局这俩个过程
  • 继承特定的View(比如TextView)
    这种方法一般用于扩展某种已有的View功能。这种方法不需要自己支持wrap_content,padding等。
  • 继承特定的ViewGroup(比如LinearLayout等)
    当某种效果很像几种View组合在一起的时候就可以采用这种方法。这种方法不需要自己处理ViewGroup的测量和布局这俩个过程。

图片 5

下面就用代码展示下自定义View的基本步骤:
  • 新建BasicCustomView继承View
    完整代码如下
public class BasicCustomView extends View {

    private Paint mPaint;

    public BasicCustomView(Context context) {
        super(context);
        initView();
    }

    public BasicCustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public BasicCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView() {
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
       canvas.translate(width/2,height/2);
        canvas.drawCircle(0,0,100,mPaint);
    }
}

首先验证自定义View是否支持layout_margin,padding,wrap_content等属性,验证代码如下:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_custom_view_basic"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.ahuang.viewandgroup.activity.CustomViewBasicActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
        <com.example.ahuang.viewandgroup.View.BasicCustomView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="20dp"
            android:background="#111fff"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
        <com.example.ahuang.viewandgroup.View.BasicCustomView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#111fff"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
        <com.example.ahuang.viewandgroup.View.BasicCustomView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#111fff"
            android:padding="20dp"/>
    </LinearLayout>

</LinearLayout>

图片 6

上图证明图我们的自定义View
1.支持layout_margin属性
2.不支持padding属性
3.证明不支持wrap_content

2.继承View重写onDraw的方法。(例如实现圆形view)

图片 7

by 吴思博

2.通过实践有了更深的体会。

图3.网易云阅读界面预览

4.继承ViewGroup,需要自己处理ViewGroup的测量和布局的两个过程。

3、如果每个item可以实现自定义view,处理好如下方法即可。

Tab是每个item的mode类。Tab内部类定义了item的成员变量,并set和get方法,然后又封装了Tab的属性设置方法。

图片 8

图片 9

图片 10

由一个个单独的Fragment改成了TabLayout的形式,往后翻几个之前的Fragment容易被回收,这里就需要恢复Fragment了,通onSaveInstanceState()方法实现。

图片 11

图片 12

发现了两个单行的开源指示器还不错,有兴趣可以看看

一、实现思路(如何实现?)

3、源码中选择tab的关键实现:

1.一种自定义View可能有多种实现方式、我们要找到代价最小、最高效的方式。

虽然Android提供了一套GUI库,里面有很多控件,但是很多时候我们并不满足于系统的控件,通过自定义view,我们可以实现各种五花八门的效果,但是自定义控件有一定难度,尤其是复杂的自定义控件。

lTab类和TabView类和SlidingTabStrip类为TabLayout提供了三个基本的元素。

3.继承特定的ViewGroup。比如LinearLayout,几种常见View组合在一起的时候,可以采用此方法。不需要自己处理ViewGroup的测量和布局的两个过程。

图1.网易新闻单行选择条

本文由美高梅网址注册发布于澳门mgm4858集团登录网址,转载请注明出处:三、自定义实现主要过程,在实现效果的基础上

关键词: