常用UI

Shader 着色器

1,BitmapShader 位图着色器 可以实现圆形,圆角矩形等图片

Paint paint = new Paint();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint.setShader(bitmapShader);
canvas.drawCircle(30,30,20,paint);

2,LinearGradient 线性 着色器

3, RadialGradient 光束着色器

4,SweepGradient 梯度着色器

5,ComposeGradient 混合着色器

Shader.TileMode.CLAMP 拉伸不断重复

Shader.TileMode.REPEAT 重复

Shader.TileMode.MIRROR 横向不断反转重复

Matrix 让图片变换

Translate 平移

Rotate 旋转

Scale 缩放

Skew 错切

让bitmap 倒过来

Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
int w = srcBitmap.getWidth();
int h = srcBitmap.getHeight();
Matrix matrix = new Matrix();
matrix.setScale(1F,-1F);
Bitmap bitmap = Bitmap.createBitmap(srcBitmap,0,0,w,h,matrix,true);
canvas.drawBitmap(bitmap,0,0,null);

PorterDuffXfermode 交集并集

可以实现刮刮乐的效果 和一些 圆角 圆形图片的效果

void porterDuffXfermode(Canvas outCanvas){
    Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.timg);
    int mw = srcBitmap.getWidth();
    int mh = srcBitmap.getHeight();
    Bitmap mOut = Bitmap.createBitmap(mw,mh,Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(mOut);
    int r = Math.min(mw,mh)/2;
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    canvas.drawCircle(mw/2,mh/2,r,paint);
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    canvas.drawBitmap(srcBitmap,0,0,paint);

    int w = getWidth();
    int h = getHeight();
    outCanvas.drawBitmap(mOut,w/2-mw/2,h/2-mh/2,null);
}

倒影效果


/**
 * 实现一个简单的倒影
 */
public class ReflectView  extends View {
    public ReflectView(Context context) {
        super(context);
        init(context);
    }

    public ReflectView(Context context,  AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ReflectView(Context context,  AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    Bitmap mSrcBitmap;
    Bitmap mRefBitmap;
    Bitmap mOut;
    void init(Context context){
        mSrcBitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.timg);
        Matrix matrix = new Matrix();
        matrix.setScale(1,-1);
        mRefBitmap = Bitmap.createBitmap(mSrcBitmap,0,0,mSrcBitmap.getWidth(),mSrcBitmap.getHeight(),matrix,true);

        mOut = Bitmap.createBitmap(mSrcBitmap.getWidth(),mSrcBitmap.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(mOut);
        Paint paint = new Paint();
        paint.setAntiAlias(true);

        paint.setShader(new LinearGradient(0,mSrcBitmap.getHeight(),0,
                mSrcBitmap.getHeight()+mSrcBitmap.getHeight()/4,
                0XDD000000,0X10000000, Shader.TileMode.CLAMP));
        canvas.drawRect(0,0,mOut.getWidth(),mOut.getHeight(),paint);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(mRefBitmap,0,0,paint);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.BLACK);
        canvas.drawBitmap(mSrcBitmap,0,0,null);
        canvas.drawBitmap(mOut,0,mSrcBitmap.getHeight(),null);
    }
}

PathEffect

默认没有效果

CornerPathEffect 将Path拐角处变的圆滑

DiscretePathEffect 线段上产生杂点

DashPathEffect 线段上产生虚线的效果

PathDashPathEffect 和上面类似 但是它可以让虚线有形状如 三角 点

ComposePathEffect 组合path 效果

void pathEffect(Canvas canvas){
    Path mPath = new Path();
    mPath.moveTo(0,0);
    for (int i = 0; i <30 ; i++) {
        mPath.lineTo(i*35, (float) (Math.random()*50));
    }

    Paint mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(2);

    PathEffect [] mEffects = new PathEffect[6];
    mEffects[0] = null;
    mEffects[1] = new CornerPathEffect(30);
    mEffects[2] = new DiscretePathEffect(3.0F,5.0F);
    mEffects[3] = new DashPathEffect(new float[]{20,10,5,10},0);
    Path path = new Path();
    path.addRect(0,0,8,8,Path.Direction.CCW);
    mEffects[4] = new PathDashPathEffect(path,12,0,PathDashPathEffect.Style.ROTATE);
    mEffects[5] = new ComposePathEffect(mEffects[3],mEffects[1]);

    for (int i = 0; i <mEffects.length ; i++) {
        mPaint.setPathEffect(mEffects[i]);
        canvas.drawPath(mPath,mPaint);
        canvas.translate(0,50);
    }

}

2D 绘图基础

Paint 的设置

  • setAntiAlias() 设置画笔的 锯齿效果

  • setColor 设置画笔的颜色

  • setARGB 设置画笔的ARGB 值

  • setAlpha 设置画笔的alpga 值

  • setTextSize 设置字体的尺寸

  • setStyle 设置画笔的风格 空心(Paint.Style.STROKE) 或 实心(Paint.Style.FILL) 默认为实心

  • setStrokeWidth 设置空心边框的宽度

绘制扇形 绘制圆弧 绘制实心 绘制空心

drawArc(float left, float top, float right, float bottom, float startAngle,
        float sweepAngle, boolean useCenter, @NonNull Paint paint) 

绘制扇形 Paint.Style.STROKE + useCenter(true)

绘制圆弧 Paint.Style.STROKE + useCenter(false)

绘制扇形实心 Paint.Style.FILL+ useCenter(true)

绘制圆弧实心Paint.Style.FILL+ useCenter(false)

在指定的位置绘制文本

void textPath(Canvas canvas){
        Path mPath = new Path();
        mPath.moveTo(0,0);
        for (int i = 0; i <30 ; i++) {
            mPath.lineTo(i*35, (float) (Math.random()*50));
        }
        Paint mPaint = new Paint();
        mPaint.setTextSize(18);
        canvas.drawTextOnPath("xxxxxxxxxxxxxxxxxxx",mPath,20,10,mPaint);
    }

XML 绘图

BitMap

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android" android:src="@drawable/ic_arrow_upward_black_24dp">

</bitmap>

Shape

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <corners android:topLeftRadius="10dp"
        android:topRightRadius="10dp"
        android:bottomLeftRadius="10dp"
        android:bottomRightRadius="10dp"/>
    <gradient android:angle="30"
        android:centerX="100"
        android:centerY="100"
        android:centerColor="#FF0000"
        android:endColor="#000FFF"
        android:gradientRadius="10"
        android:startColor="#00FF00"
        android:type="linear"
        android:useLevel="true"/>
    <padding android:left="1dp"
        android:right="1dp"
        android:top="1dp"
        android:bottom="1dp"
        />
    <size android:width="200dp"
        android:height="200dp"/>
    <solid android:color="#eeaaee"/>
    <stroke android:width="5dp"
        android:color="#aaeeaa"
        android:dashWidth="5dp"
        android:dashGap="6dp"
        />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <gradient android:startColor="#FF5DA2FF"
        android:endColor="#805FBBFF"
        android:angle="45"/>
    <padding android:left="7dp"
        android:top="7dp"
        android:right="7dp"
        android:bottom="7dp"/>
    <corners android:radius="10dp"/>

</shape>

Layer

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_arrow_upward_black_24dp" />
    <item android:drawable="@drawable/ic_arrow_upward_black_24dp"
        android:left="10dp"
        android:top="10dp"
        android:right="10dp"
        android:bottom="10dp"
        />
</layer-list>

Selector

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <!--默认背景颜色 -->
    <item android:drawable="@drawable/shape"/>
    <!--没有焦点时的背景颜色-->
    <item android:state_window_focused="false" android:drawable="@drawable/shape"/>
    <!--非触摸模式下获得焦点并单机时的背景图片-->
    <item android:state_focused="true"
        android:state_pressed="true"
        android:drawable="@drawable/shape"/>
    <!--触摸模式下单击时背景颜色-->
    <item android:state_focused="false"
        android:state_pressed="true"
        android:drawable="@drawable/shape2"
        />
    <!--选中时的背景色 -->
    <item android:state_selected="true"
        android:drawable="@drawable/shape2"/>
    <!--获得焦点时的背景色-->
    <item android:state_focused="true"
        android:drawable="@drawable/shape"/>
    
</selector>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <!--填充颜色 -->
            <solid android:color="#FF889900"/>
            <corners android:radius="10dp"/>
            <!-- 内容与边界的间隔 -->
            <padding android:bottom="10dp"
                android:left="10dp"
                android:right="10dp"
                android:top="10dp"/>
        </shape>

    </item>

    <item >
        <shape android:shape="rectangle">
            <!--填充颜色 -->
            <solid android:color="#FFFF9900"/>
            <corners android:radius="10dp"/>
            <!-- 内容与边界的间隔 -->
            <padding android:bottom="10dp"
                android:left="10dp"
                android:right="10dp"
                android:top="10dp"/>
        </shape>
    </item>
</selector>

SVG 动画

结合属性动画 应用可以改变的属性

android:propertyName="pathData"
android:propertyName="rotation"
android:propertyName="trimPathStart"  

vector2.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="200dp"
    android:width="200dp"
    android:viewportHeight="100"
    android:viewportWidth="100">


    <group android:name="sun"
        android:pivotX="60"
        android:pivotY="50"
        android:rotation="0"
        >
        <path android:name="path_sun"
            android:fillColor="#FF0000"
            android:pathData="M 50,50
                                a 10,10 0 1,0 20,0
                                a 10,10 0 1,0,-20,0"/>

        <group android:name="earth"
            android:pivotY="50"
            android:pivotX="75"
            android:rotation="0">

            <path android:name="path_earth"
                android:fillColor="#00FF00"
                android:pathData="M 70,50
                                    a 5,5 0 1,0 10,0
                                      a 5,5 0 1,0 -10,0   "/>
            <group >

                <path android:name="path_earth"
                    android:fillColor="#000000"
                    android:pathData="M 90,50
                                    a 4,4 0 1,0 8,0
                                      a 4,4 0 1,0 -8,0   "/>

            </group>

        </group>


    </group>

</vector>

anim01.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="4000"
    android:propertyName="rotation"
    android:valueFrom="0"
    android:valueTo="360"
    />

anim02.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="4000"
    android:propertyName="rotation"
    android:valueFrom="0"
    android:valueTo="360"
    />

animated_vector02.xml

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:drawable="@drawable/vector2"
    tools:ignore="NewApi">

    <target
        android:animation="@animator/anim01"
        android:name="sun"/>
    <target
        android:animation="@animator/anim02"
        android:name="earth"/>

</animated-vector>

使用

AnimatedVectorDrawable drawable = (AnimatedVectorDrawable)getDrawable(R.drawable.animated_vector02);
img.setImageDrawable(drawable);
drawable.start();

动画

视图动画

优点:效率高 使用方便

缺点:不具备交互性 响应事件的位置不会发生改变

视图动画包括 :AlphaAnimation RotateAnimation TranslateAnimation ScaleAnimation

//透明度动画 AlphaAnimation(float fromAlpha, float toAlpha)  最小 0 最大 1 
AlphaAnimation alpha = new AlphaAnimation(0,1);
alpha.setDuration(1000);
v.startAnimation(alpha);
//旋转动画  0 到 360 中心点对齐   锚点
RotateAnimation rotate = new RotateAnimation(0,360,RotateAnimation.RELATIVE_TO_SELF,0.5F,RotateAnimation.RELATIVE_TO_SELF,0.5F);
                rotate.setDuration(2000);
                v.startAnimation(rotate);
//平移TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) 
TranslateAnimation animation = new TranslateAnimation(0,0,300,0);
                animation.setDuration(2000);
                v.startAnimation(animation);


//缩放动画
v.clearAnimation();
                ScaleAnimation animation = new ScaleAnimation(1,2,1,2, Animation.RELATIVE_TO_SELF,0.5F,Animation.RELATIVE_TO_SELF,0.5F);
                animation.setDuration(2000);
                animation.setFillAfter(true); // 当动画结束后 不再回到原来的状态
                v.setAnimation(animation);


//动画集合
AnimationSet animationSet = new AnimationSet(false);

                TranslateAnimation translateAnimation = new TranslateAnimation(0,0,300,0);
                translateAnimation.setDuration(2000);
                animationSet.addAnimation(translateAnimation);

                ScaleAnimation scaleAnimation = new ScaleAnimation(1,2,1,2, Animation.RELATIVE_TO_SELF,0.5F,Animation.RELATIVE_TO_SELF,0.5F);
                scaleAnimation.setDuration(2000);
                animationSet.addAnimation(scaleAnimation);

                v.clearAnimation();
                v.startAnimation(animationSet);


//动画的监听回调
 animationSet.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
                        
                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {

                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {

                    }
                });

属性动画

ObjectAnimator

ObjectAnimator可以自动驱动 可以调用setFrameDelay(frameDelay) 设置动画帧之间的间隙时间来调整帧率 来减少CPU 资源的消耗

ObjectAnimator animator = ObjectAnimator.ofFloat(v,"translationX",300);
animator.setDuration(2000);
animator.start();

ObjectAnimator animator = ObjectAnimator.ofFloat(v,"rotation",v.getRotation()+300);
                animator.setDuration(2000);
                animator.start();

View 常用的操作属性

  • translationX 和 translationY
  • rotation 绕Z 轴 ,rotationX 绕X 轴 rotationY 绕Y 轴
  • scaleX scaleY
  • pivotX pivotY 锚点位置
  • x 和 y 这两个属性实用 它描述了View 对象在它的容器中的最终位置 x = view 左边 + translationX y同理
  • alpha 1 不透明 0 完全透明
AnimatorSet

注意 视图动画中的是 AnimationSet

ObjectAnimator animator1 = ObjectAnimator.ofFloat(v, "translationX", 300,0,-300);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(v, "scaleX", 1f, 0f, 1f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(v, "scaleY", 1f, 0, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(2000);
animatorSet.playTogether(animator1, animator2, animator3);
animatorSet.start();
属性动画XMl

在 res 下的 animator 文件夹下

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:propertyName="scaleX"
    android:valueFrom="1.0"
    android:valueTo="2.0"
    android:valueType="floatType"
    >

</objectAnimator>

使用

Animator animator = AnimatorInflater.loadAnimator(AnimActivity.this,R.animator.obj_demo);
animator.setTarget(v);
animator.start();
View的animate 方法
// y 代表的是 View 在容器的最终位置 y = view的左上角位置+ translationY
v.animate().alpha(0).y(300).setDuration(3000)
        .withStartAction(new Runnable() {
            @Override
            public void run() {

            }
        }).withEndAction(new Runnable() {
    @Override
    public void run() {

    }
}).start();

Android布局动画

它是作用在ViewGroup 上 当添加View 是显示一个过度的动画

默认可以在xml 中设置 android:animateLayoutChanges=“true” 来开启

ScaleAnimation sa = new ScaleAnimation(0,1,0,1);
sa.setDuration(2000);
LayoutAnimationController lac = new LayoutAnimationController(sa,0.5f);
//ORDER_NORMAL正常 ORDER_REVERSE 反序   ORDER_RANDOM 随机
lac.setOrder(LayoutAnimationController.ORDER_NORMAL);
// 为ViewGroup 设置动画
linearLay.setLayoutAnimation(lac);

插值器 Interpolators

是加速 还是 减速 还是匀速 还是类似物理的弹性

<scale
        android:duration="2000"
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="0"
        android:toYScale="0"/>

java 中

animation.setInterpolator(new AccelerateInterpolator());

自定义动画

static class MyAnimation extends Animation {
    int mCenterWidth;
    int mCenterHeight;
    float mRotateY = 60;
    Camera mCamera;
    public MyAnimation(){
        mCamera = new Camera();
    }
    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);

        setInterpolator(new BounceInterpolator());
        mCenterWidth = width/2;
        mCenterHeight = height/2;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
        Matrix matrix = t.getMatrix();
        mCamera.save();
        mCamera.rotateY(mRotateY * interpolatedTime);
        mCamera.getMatrix(matrix);
        mCamera.restore();
        matrix.preTranslate(mCenterWidth,mCenterHeight);
        matrix.postTranslate(-mCenterWidth,-mCenterHeight);
    }
}

路径动画 PathMeasure

//创建 forceClosed Path 是否关闭 
PathMeasure(Path path, boolean forceClosed)
//计算路径的长度  如何不是闭合 计算的就不是闭合的长度  
pathMeasure.getLength();    
// 用于循环 下一个 
pathMeasure.nextContour();  
//startD 开始截取的长度
//stopD 结束截取的长度
//dst 把值 输出到Path
//起始点是否启用moveTo
pathMeasure.getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)

// 画圆 动画
Path circlePath;
    PathMeasure circlePathMeasure;

    Path path;
    Paint paint;

    float animValue;
    void init(){
        paint =new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(4);
        paint.setColor(Color.BLACK);

        path = new Path();
        circlePath = new Path();
        circlePath.addCircle(100,100,50, Path.Direction.CW);

        circlePathMeasure = new PathMeasure(circlePath,true);

        ValueAnimator animator =  ValueAnimator.ofFloat(0,1);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                animValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.setDuration(2000);
        animator.start();

    }

    @Override
    protected void onDraw(Canvas canvas) {
       canvas.drawColor(Color.WHITE);
       float stop = circlePathMeasure.getLength() * animValue;
       path.reset();
       circlePathMeasure.getSegment(0,stop,path,true);
       canvas.drawPath(path,paint);
    }
//distance 距离起始点长度
//pos [0] x 坐标 [1] y坐标
// 该点的正切值 使用方式 
// float degress = Math.atan2(tan[1],tan[0]) * 180/Math.PI; 这个获取的是角度 
// Math.atan2(tan[1],tan[0]) 这个获取的是弧度值

pathMeasure.getPosTan(float distance, float pos[], float tan[])  
 // 把位置 和 角度 存入到 矩阵中
 pathMeasure.getMatrix(stop,matrix,PathMeasure.POSITION_MATRIX_FLAG|PathMeasure.TANGENT_MATRIX_FLAG);
   
    
Path circlePath;
PathMeasure circlePathMeasure;

Path path;
Paint paint;

float animValue;
void init(){
    bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
    paint =new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(4);
    paint.setColor(Color.BLACK);

    path = new Path();
    circlePath = new Path();
    circlePath.addCircle(100,100,50, Path.Direction.CW);

    circlePathMeasure = new PathMeasure(circlePath,true);

    ValueAnimator animator =  ValueAnimator.ofFloat(0,1);
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            animValue = (float) animation.getAnimatedValue();
            invalidate();
        }
    });
    animator.setDuration(2000);
    animator.start();

}

Bitmap bitmap;

float [] pos = new float[2];
float [] tan = new float[2];

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawColor(Color.WHITE);
    float stop = circlePathMeasure.getLength() * animValue;
    path.reset();
    circlePathMeasure.getSegment(0,stop,path,true);
    canvas.drawPath(path,paint);

    circlePathMeasure.getPosTan(stop,pos,tan);
    float degress = (float) (Math.atan2(tan[1],tan[0]) * 180.0/Math.PI);
    Matrix matrix = new Matrix();
    matrix.postRotate(degress);
    matrix.postTranslate(pos[0],pos[1]);
    canvas.drawBitmap(bitmap,matrix,paint);

}  

Material Design 主题

默认三种主题

<style name="dark" parent="android:Theme.Material"/>
<style name="light" parent="android:Theme.Material.Light"/>
<style name="darkBar" parent="android:Theme.Material.Light.DarkActionBar"/>

视图与阴影

android:elevation="10dp"

代码中控制

themeBinding = DataBindingUtil.setContentView(this,R.layout.activity_theme);
themeBinding.imageView2.setTranslationZ(100);

着色 Tinting

<ImageView
    android:tint="#FF0000"
     />

<ImageView
    android:tint="#FF0000"
    android:tintMode="add"
   />

<ImageView
    android:tint="#FF0000"
    android:tintMode="multiply"/>

裁剪 Clipping

可以裁剪出不同的形状

ViewOutlineProvider outlineProvider = new ViewOutlineProvider() {
    @Override
    public void getOutline(View view, Outline outline) {
        outline.setRoundRect(0,0,view.getWidth(),view.getHeight(),30);
    }
};
themeBinding.textView.setOutlineProvider(outlineProvider);
themeBinding.textView.setClipToOutline(true);

Activity 过渡动画

Material Design 动画

Ripple 波纹效果

//波纹有边界 
android:background="?android:attr/selectableItemBackground"
//波纹超出边界
android:background="?android:attr/selectableItemBackgroundBorderless"    

创建波纹资源

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="#00FF00">

    <item >
        <shape android:shape="oval">
            <solid android:color="@color/colorPrimary"/>
        </shape>
    </item>

</ripple>

使用

android:background="@drawable/ripple_demo"

Circular Reveal 圆形提示

public static Animator createCircularReveal(
    View view,
    int centerX, // 动画开始的中心 X  
    int centerY,  // 动画开始的中心 Y  
    float startRadius, // 动画的开始半径
    float endRadius   // 动画的结束半径
) {
    return new RevealAnimator(view, centerX, centerY, startRadius, endRadius);
}
Animator animator = ViewAnimationUtils.createCircularReveal(v,v.getWidth()/2,
        v.getHeight()/2,v.getWidth(),0);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.setDuration(2000);
animator.start();

Activity状态切换动画

Transition过度动画

  • 进入
  • 退出
  • 共享元素

进入和退出的动画有

  • explode 分解
  • slide 滑动
  • fade 淡出

共享元素

  • changeBounds — 改变目标视图的布局边界
  • changeClipBounds — 裁剪目标视图边界
  • changeTransform – 改变目标视图的缩放比例和旋转角度
  • changeImageTransform --改变目标图片的大小和缩放比率
//Transition过度动画
Intent intent = new Intent(ActivityA.this,ActivityB.class);
                startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(ActivityA.this).toBundle());

protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
    
    getWindow().setEnterTransition(new Explode());
    getWindow().setEnterTransition(new Slide());
    getWindow().setEnterTransition(new Fade());
    
    getWindow().setExitTransition(new Explode());
    getWindow().setExitTransition(new Slide());
    getWindow().setExitTransition(new Fade());
    
    setContentView(R.layout.activity_b);

}
<ImageView
    android:id="@+id/imageView5"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:transitionName="img"/>
Intent intent = new Intent(ActivityA.this,ActivityB.class);
Pair<View, String> []  sharedElements = new Pair[1];
sharedElements[0] = Pair.create(ImageView,"img");

startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(ActivityA.this,sharedElements).toBundle())   
    
    
 //ActivityB
  <ImageView
        android:transitionName="img"
        android:id="@+id/imageView4"
        android:layout_width="200dp"
        android:layout_height="200dp"
        app:srcCompat="@drawable/ic_touch_app_black_24dp" />
  @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
        //getWindow().setEnterTransition(new Explode());
        getWindow().setEnterTransition(new Slide());
        //getWindow().setEnterTransition(new Fade());
        //getWindow().setExitTransition(new Explode());
        getWindow().setExitTransition(new Slide());
        //getWindow().setExitTransition(new Fade());
        setContentView(R.layout.activity_b);
        View view = findViewById(R.id.imageView4)

    }           

res/transition

<?xml version="1.0" encoding="utf-8"?>
<transitionSet
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:interpolator="@android:interpolator/linear_out_slow_in">

    <changeBounds>
        <!--suppress AndroidElementNotAllowed -->
        <arcMotion
            android:maximumAngle="90"
            android:minimumHorizontalAngle="90"
            android:minimumVerticalAngle="0"/>
    </changeBounds>
</transitionSet>
// 入场动画
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setupEnterAnimation() {
    Transition transition = TransitionInflater.from(this)
            .inflateTransition(R.transition.arc_motion);
    transition.addListener(new Transition.TransitionListener() {
        @Override public void onTransitionStart(Transition transition) {

        }

        @Override public void onTransitionEnd(Transition transition) {
            transition.removeListener(this);
            animateRevealShow();
        }

        @Override public void onTransitionCancel(Transition transition) {

        }

        @Override public void onTransitionPause(Transition transition) {

        }

        @Override public void onTransitionResume(Transition transition) {

        }
    });
    getWindow().setSharedElementEnterTransition(transition);
}


 // 动画展示
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void animateRevealShow() {
        GuiUtils.animateRevealShow(
                this, mRlContainer,
                mFabCircle.getWidth() / 2, R.color.colorAccent,
                new GuiUtils.OnRevealAnimationListener() {
                    @Override public void onRevealHide() {

                    }

                    @Override public void onRevealShow() {
                        initViews();
                    }
                });
    }



// 退出动画
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void setupExitAnimation() {
        Fade fade = new Fade();
        fade.setDuration(300);
        getWindow().setReturnTransition(fade);
    }

/**
 * 动画效果
 */
public class GuiUtils {
    public interface OnRevealAnimationListener {
        void onRevealHide();

        void onRevealShow();
    }

    // 圆圈爆炸效果显示
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static void animateRevealShow(
            final Context context, final View view,
            final int startRadius, @ColorRes final int color,
            final OnRevealAnimationListener listener) {
        int cx = (view.getLeft() + view.getRight()) / 2;
        int cy = (view.getTop() + view.getBottom()) / 2;

        //计算出斜边
        float finalRadius = (float) Math.hypot(view.getWidth(), view.getHeight());

        // 设置圆形显示动画
        Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, startRadius, finalRadius);
        anim.setDuration(300);
        anim.setInterpolator(new AccelerateDecelerateInterpolator());
        anim.addListener(new AnimatorListenerAdapter() {
            @Override public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                view.setVisibility(View.VISIBLE);
                listener.onRevealShow();
            }

            @Override public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                view.setBackgroundColor(ContextCompat.getColor(context, color));
            }
        });

        anim.start();
    }

    // 圆圈凝聚效果
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static void animateRevealHide(
            final Context context, final View view,
            final int finalRadius, @ColorRes final int color,
            final OnRevealAnimationListener listener
    ) {
        int cx = (view.getLeft() + view.getRight()) / 2;
        int cy = (view.getTop() + view.getBottom()) / 2;
        int initialRadius = view.getWidth();
        // 与入场动画的区别就是圆圈起始和终止的半径相反
        Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, initialRadius, finalRadius);
        anim.setDuration(300);
        anim.setInterpolator(new AccelerateDecelerateInterpolator());
        anim.addListener(new AnimatorListenerAdapter() {
            @Override public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                view.setBackgroundColor(ContextCompat.getColor(context, color));
            }

            @Override public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                listener.onRevealHide();
                view.setVisibility(View.INVISIBLE);
            }
        });
        anim.start();
    }
}

通知栏

创建通知

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.baidu.com"));
PendingIntent pendingIntent =  PendingIntent.getActivity(context,0,intent,0);
Notification.Builder builder = new Notification.Builder(context);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentIntent(pendingIntent);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));
builder.setContentTitle("title");
builder.setContentText("content");
builder.setSubText("sub txt");

发出通知

NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.notify(1,builder.build());

折叠式通知栏

悬挂式

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.baidu.com"));
PendingIntent pendingIntent =  PendingIntent.getActivity(context,0,intent,0);

Notification.Builder builder = new Notification.Builder(context);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setPriority(Notification.PRIORITY_DEFAULT);
builder.setCategory(Notification.CATEGORY_MESSAGE);
builder.setContentTitle("title");
builder.setContentText("text");
// 变成悬挂式通知
builder.setFullScreenIntent(pendingIntent,true);

NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.notify(1,builder.build());

通知栏等级

//没有锁屏的情况会显示
builder.setVisibility(Notification.VISIBILITY_PRIVATE);
//任何情况下都会显示
builder.setVisibility(Notification.VISIBILITY_PUBLIC);
//在 pin password 等安全 和没有锁屏的情况下会显示
builder.setVisibility(Notification.VISIBILITY_SECRET);

android 信息的获取

android.os.Build

StringBuilder builder = new StringBuilder();

//主板
String board = Build.BOARD;
builder.append("主板:"+board+"  ");
//android 系统定制商
String brand = Build.BRAND;
builder.append("系统定制商:"+brand+"  ");

//CPU 指令集
String[] supportedAbis = Build.SUPPORTED_ABIS;
for (int i = 0; i <supportedAbis.length ; i++) {
    builder.append("指令集:"+supportedAbis[i]+"  ");
}


//设备参数
String device = Build.DEVICE;
builder.append("设备参数:"+device+"  ");

//显示屏参数
String display = Build.DISPLAY;
builder.append("显示屏参数:"+display+"  ");

// 唯一编号
String fingerprint = Build.FINGERPRINT;
builder.append("唯一编号:"+fingerprint+"  ");

//硬件序列号
String serial = Build.SERIAL;
builder.append("硬件序列号:"+serial+"  ");

// 修订版本列表
String id = Build.ID;
builder.append("修订版本列表:"+id+"  ");

// 硬件制造商
String manufacturer = Build.MANUFACTURER;
builder.append("硬件制造商:"+manufacturer+"  ");

// 版本
String model = Build.MODEL;
builder.append("版本:"+model+"  ");

//硬件名
String hardware = Build.HARDWARE;
builder.append("硬件名:"+hardware+"  ");

//手机产品名
String product = Build.PRODUCT;
builder.append("手机产品名:"+product+"  ");

//描述Build 标签
String tags = Build.TAGS;
builder.append("描述Build 标签:"+tags+"  ");
// 类型
String type = Build.TYPE;
builder.append("类型:"+type+"  ");
//当前开发代号
String codename = Build.VERSION.CODENAME;
builder.append("当前开发代号:"+codename+"  ");
//源码控制版本号
String incremental = Build.VERSION.INCREMENTAL;
builder.append("源码控制版本号:"+incremental+"  ");
//版本字符串
String release = Build.VERSION.RELEASE;
builder.append("版本字符串:"+release+"  ");
//版本号
int sdkInt = Build.VERSION.SDK_INT;
builder.append("版本号:"+sdkInt+"  ");
//host 值
String host = Build.HOST;
builder.append("host 值:"+host+"  ");
//User 名
String user = Build.USER;
builder.append("User 名:"+user+"  ");
//编译时间
long time = Build.TIME;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
builder.append("编译时间:"+dateFormat.format(new Date(time))+"  ");

Log.e("vic-zhang",builder.toString());

SystemProperty 系统属性

StringBuilder builder = new StringBuilder();
//OS 版本
String version = System.getProperty("os.version");
builder.append("OS 版本:"+version+"  ");
//OS 名称
String name = System.getProperty("os.name");
builder.append("OS 名称:"+name+"  ");
//OS 架构
String arch = System.getProperty("os.arch");
builder.append("OS 架构:"+arch+"  ");
//Home 属性
String userHome = System.getProperty("user.home");
builder.append("Home 属性:"+userHome+"  ");
//Name 属性
String userName = System.getProperty("user.name");
builder.append("Name 属性:"+userName+"  ");
//Dir 属性
String userDir = System.getProperty("user.dir");
builder.append("Dir 属性:"+userDir+"  ");
//时区
String userTimeZone = System.getProperty("user.timezone");
builder.append("时区:"+userTimeZone+"  ");
//路径分割符
String separator = System.getProperty("path.separator");
builder.append("路径分割符:"+separator+"  ");
//行分割符
String lineSeparator = System.getProperty("line.separator");
builder.append("行分割符:"+lineSeparator+"  ");
//文件分割符
String fileSeparator = System.getProperty("file.separator");
builder.append("文件分割符:"+fileSeparator+"  ");
//java vendor url 属性
String vendorUrl = System.getProperty("java.vendor.url");
builder.append("java vendor url 属性:"+vendorUrl+"  ");
//java class 路径
String classPath = System.getProperty("java.class.path");
builder.append("java class 路径:"+classPath+"  ");
//java class 版本号
String classVersion = System.getProperty("java.class.version");
builder.append("java class 版本号:"+classVersion+"  ");
//java vendor 属性
String vendor = System.getProperty("java.vendor");
builder.append("java vendor 属性:"+vendor+"  ");
// java 版本
String javaVersion = System.getProperty("java.version");
builder.append("java 版本:"+javaVersion+"  ");
// java home 属性
String javaHome = System.getProperty("java.home");
builder.append("java home 属性:"+javaHome+"  ");
Log.e("vic-zhang",builder.toString());

ViewStub

ViewStub viewStub = findViewById(R.id.viewstub);
if (viewStubLayout==null){
    //填充布局
    viewStubLayout = viewStub.inflate();
}

PagerAdapter

ViewPager 的适配器

它的子类有

FragmentStatePagerAdapter:适合大量页面,不断重建和销毁

FragmentPagerAdapter:适合少量页面,常驻内存。

SmartRefreshLayout 刷新

智能刷新控件的使用

implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0'  
implementation 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.0'  

https://github.com/scwang90/SmartRefreshLayout

嵌套滚动问题

当 NestScrollView 中嵌套 RecyclerView 时 上拉刷新问题: 当数据加载完成时 提示加载完成时 会出现加载完成 覆盖数据的问题

解决:当数加载成功后 过500 毫秒在刷新数据 因为它有一个 500 毫秒的动画

ArrayList<String> arrayList = new ArrayList<>();
for (int i = 0; i <20 ; i++) {
    arrayList.add(String.valueOf(i));
}

final RecyclerView recycleView = findViewById(R.id.recycleView);
recycleView.setLayoutManager(new LinearLayoutManager(this));
final Adapter adapter = new Adapter(arrayList);
recycleView.setAdapter(adapter);

ViewCompat.setNestedScrollingEnabled(recycleView, false);
SmartRefreshLayout smartRefreshLayout = findViewById(R.id.refreshLayout);

smartRefreshLayout.setNestedScrollingEnabled(true);



smartRefreshLayout.setRefreshHeader(new ClassicsHeader(this));
smartRefreshLayout.setRefreshFooter(new MyClassicsFooter(this));

//smartRefreshLayout.canLoadMore();
//smartRefreshLayout.setReboundDuration(0);

smartRefreshLayout.setEnableLoadMore(true);

smartRefreshLayout.setPrimaryColors();
smartRefreshLayout.setPrimaryColors(0xffff0000, 0xff6ea9ff);

smartRefreshLayout.setScrollBoundaryDecider(new ScrollBoundaryDecider() {
    @Override
    public boolean canRefresh(View content) {
        if (recycleView == null) return false;
        if (recycleView.computeVerticalScrollOffset()==0)
            return true;
        return false;
    }

    @Override
    public boolean canLoadMore(View content) {
        if (recycleView == null) return false;
        //获取recyclerView的高度
        recycleView.getHeight();
        //整个View控件的高度
        int scrollRange = recycleView.computeVerticalScrollRange();
        //当前屏幕之前滑过的距离
        int scrollOffset = recycleView.computeVerticalScrollOffset();
        //当前屏幕显示的区域高度
        int scrollExtent = recycleView.computeVerticalScrollExtent();
        int height = recycleView.getHeight();
        if(height>scrollRange){
            return false;
        }
        if (scrollRange <=scrollOffset+scrollExtent){
            return true;
        }
        return false;
    }
});



smartRefreshLayout.setOnRefreshListener(new OnRefreshListener() {
    @Override
    public void onRefresh(final RefreshLayout refreshLayout) {
        refreshLayout.getLayout().postDelayed(new Runnable() {
            @Override
            public void run() {
                adapter.refresh();
                refreshLayout.finishRefresh();
                refreshLayout.resetNoMoreData();//setNoMoreData(false);
            }
        }, 2000);
    }
});

smartRefreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
    @Override
    public void onLoadMore(@NonNull final RefreshLayout refreshLayout) {
        refreshLayout.getLayout().postDelayed(new Runnable() {
            @Override
            public void run() {
                if (adapter.getItemCount() > 100) {
                    Toast.makeText(getApplication(), "数据全部加载完毕", Toast.LENGTH_SHORT).show();
                    refreshLayout.finishLoadMoreWithNoMoreData();//将不会再次触发加载更多事件
                } else {
                    // 在这个地方修改 我改成了600 毫秒 改成500 毫秒合理
                    refreshLayout.getLayout().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            adapter.loadMore();
                        }
                    },600);
                    refreshLayout.finishLoadMore();
                }
            }
        }, 2000);
    }
});

FalsifyHeader 设置颜色问题

想让一个控件有弹性效果 但又想设置颜色 如何是上下都是纯色可以直接设置背景色

但我想要的是 可以弹性滚动 但又要 header 和 footer 设置不同的颜色

我们可以自定义 FalsifyHeader

// 使用
FalsifyHeader falsifyHeader =new MyFalsifyHeader(this); //new FalsifyHeader(this);
falsifyHeader.setBackgroundResource(R.color.colorPrimary);

smartRefreshLayout.setRefreshHeader(falsifyHeader);
smartRefreshLayout.setRefreshFooter(new FalsifyFooter(this));

smartRefreshLayout.setEnablePureScrollMode(false);
//smartRefreshLayout.setPrimaryColors(0xFFFF0000);
smartRefreshLayout.setPrimaryColors(0xffff0000, 0xff6ea9ff);


public static class MyFalsifyHeader extends FalsifyHeader{

        public MyFalsifyHeader(Context context) {
            super(context);
        }

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

        int mBackgroundColor;

        @Override
        public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {
            super.onInitialized(kernel, height, maxDragHeight);
            mRefreshKernel.requestDrawBackgroundFor(this, mBackgroundColor);

        }

        @Override
        public void setPrimaryColors(final int... colors) {
            super.setPrimaryColors(colors);
            mBackgroundColor = colors[0];
            if (mRefreshKernel != null) {
                mRefreshKernel.requestDrawBackgroundFor(MyFalsifyHeader.this, colors[0]);
            }

//            post(new Runnable() {
//                @Override
//                public void run() {
//                    mRefreshKernel.requestDrawBackgroundFor(MyFalsifyHeader.this, colors[0]);
//                }
//            });


        }
    }

自定义Header

要实现 RefreshHeader 接口 RefreshHeader extends RefreshInternal

RefreshInternal extends OnStateChangedListener

如果我们实现 RefreshHeader 接口 要写很多方法

我们可以 选择继承 InternalAbstract

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:gravity="center"
    android:background="#FFFFFF">

    <ImageView
        android:id="@+id/arrowImg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"
        />

    <ImageView
        android:id="@+id/progressView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"/>

    <TextView
        android:id="@+id/headerTxt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="下拉刷新"/>

</LinearLayout>
public class MyHeader extends LinearLayout implements RefreshHeader {


    public MyHeader(Context context) {
        this(context,null);
    }

    public MyHeader(Context context,  AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyHeader(Context context,  AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    //标题头
    TextView headerTxt;
    //下拉箭头
    ImageView arrowView;
    // 刷新动画视图
    ImageView progressView;
    //刷新动画
    ProgressDrawable progressDrawable;


    void init(){
        progressDrawable = new ProgressDrawable();
        View rootView = LayoutInflater.from(getContext()).inflate(R.layout.header, this, true);
        headerTxt = rootView.findViewById(R.id.headerTxt);
        arrowView = rootView.findViewById(R.id.arrowImg);
        arrowView.setImageDrawable(new ArrowDrawable());
        progressView =  rootView.findViewById(R.id.progressView);
        progressView.setImageDrawable(progressDrawable);
        setMinimumHeight(SmartUtil.dp2px(60));
    }



    //真实的视图就是自己,不能返回null
    @NonNull
    @Override
    public View getView() {
        return this;
    }

    //指定为平移,不能null
    @NonNull
    @Override
    public SpinnerStyle getSpinnerStyle() {
        return  SpinnerStyle.Translate;
    }

    @Override
    public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
        progressDrawable.start();//开始动画
    }

    @Override
    public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) {
        progressDrawable.stop();//停止动画
        progressView.setVisibility(GONE);//隐藏动画
        if (success){
            headerTxt.setText("刷新完成");
        } else {
            headerTxt.setText("刷新失败");
        }
        return 500;//延迟500毫秒之后再弹回
    }

    @Override
    public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
        switch (newState){
            case None:
            case PullDownToRefresh://下拉开始刷新
                headerTxt.setText("下拉开始刷新");
                arrowView.setVisibility(View.VISIBLE);//显示下拉箭头
                progressView.setVisibility(View.GONE);//隐藏
                arrowView.animate().rotation(0);//还原箭头方向
                break;
            case Refreshing://正在刷新
                headerTxt.setText("正在刷新");
                progressView.setVisibility(VISIBLE);//显示加载动画
                arrowView.setVisibility(GONE);//隐藏箭头
                break;

            case ReleaseToRefresh:
                headerTxt.setText("释放立即刷新");
                arrowView.animate().rotation(180);//显示箭头改为朝上
                break;
        }
    }

    @Override
    public void setPrimaryColors(int... colors) {

    }

    @Override
    public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {

    }

    @Override
    public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) {

    }

    @Override
    public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {

    }

    @Override
    public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {

    }

    @Override
    public boolean isSupportHorizontalDrag() {
        return false;
    }


}
public class MyRefreshHeader  extends InternalAbstract implements RefreshHeader {

    private RefreshKernel mRefreshKernel;

    public MyRefreshHeader(Context context){
        super(context,null,0);
    }


    protected MyRefreshHeader(@NonNull View wrapped) {
        super(wrapped);
    }

    protected MyRefreshHeader(@NonNull View wrappedView, @Nullable RefreshInternal wrappedInternal) {
        super(wrappedView, wrappedInternal);
    }

    protected MyRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {
        mRefreshKernel = kernel;
    }

    @Override
    public void onReleased(@NonNull RefreshLayout layout, int height, int maxDragHeight) {
        if (mRefreshKernel != null) {
            mRefreshKernel.setState(RefreshState.None);
            //onReleased 的时候 调用 setState(RefreshState.None); 并不会立刻改变成 None
            //而是先执行一个回弹动画,RefreshFinish 是介于 Refreshing 和 None 之间的状态
            //RefreshFinish 用于在回弹动画结束时候能顺利改变为 None
            mRefreshKernel.setState(RefreshState.RefreshFinish);
        }
    }

}

结合SnapHelper 使用

没什么好说的 给RecyclerView 添加 SnapHelper 就行

SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

//去掉 光晕
recycleView.setOverScrollMode(View.OVER_SCROLL_NEVER);
android:overScrollMode="never"

多功能监听器

实现 OnMultiPurposeListener 该接口 不懂看源码

 /**
     * 手指拖动下拉(会连续多次调用,添加isDragging并取代之前的onPulling、onReleasing)
     * @param header 头部
     * @param isDragging true 手指正在拖动 false 回弹动画
     * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+maxDragHeight) / footerHeight )
     * @param offset 下拉的像素偏移量  0 - offset - (footerHeight+maxDragHeight)
     * @param headerHeight 高度 HeaderHeight or FooterHeight
     * @param maxDragHeight 最大拖动高度
     */
    void onHeaderMoving(RefreshHeader header, boolean isDragging, float percent, int offset, int headerHeight, int maxDragHeight);

//    void onHeaderPulling(RefreshHeader header, float percent, int offset, int headerHeight, int maxDragHeight);
//    void onHeaderReleasing(RefreshHeader header, float percent, int offset, int headerHeight, int maxDragHeight);
    void onHeaderReleased(RefreshHeader header, int headerHeight, int maxDragHeight);
    void onHeaderStartAnimator(RefreshHeader header, int headerHeight, int maxDragHeight);
    void onHeaderFinish(RefreshHeader header, boolean success);

    /**
     * 手指拖动上拉(会连续多次调用,添加isDragging并取代之前的onPulling、onReleasing)
     * @param footer 尾部
     * @param isDragging true 手指正在拖动 false 回弹动画
     * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+maxDragHeight) / footerHeight )
     * @param offset 下拉的像素偏移量  0 - offset - (footerHeight+maxDragHeight)
     * @param footerHeight 高度 HeaderHeight or FooterHeight
     * @param maxDragHeight 最大拖动高度
     */
    void onFooterMoving(RefreshFooter footer, boolean isDragging, float percent, int offset, int footerHeight, int maxDragHeight);

//    void onFooterPulling(RefreshFooter footer, float percent, int offset, int footerHeight, int maxDragHeight);
//    void onFooterReleasing(RefreshFooter footer, float percent, int offset, int footerHeight, int maxDragHeight);
    void onFooterReleased(RefreshFooter footer, int footerHeight, int maxDragHeight);
    void onFooterStartAnimator(RefreshFooter footer, int footerHeight, int maxDragHeight);
    void onFooterFinish(RefreshFooter footer, boolean success);

RefreshHeaderWrapper 包装头

布局文件 最多三个 当有三个控件时 第一个控件 不是 RefreshHeader 子类 则 生成 RefreshHeaderWrapper 来包装 第一个控件 footView 同理

<com.scwang.smartrefresh.layout.SmartRefreshLayout
    android:id="@+id/refreshLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="true"
    android:clipToPadding="true"
    app:srlDragRate="0.7"
    app:srlHeaderMaxDragRate="1.3"
    app:srlHeaderHeight="150dp"
    app:srlEnableAutoLoadMore="true"
    app:srlHeaderInsetStart="?attr/actionBarSize"  //偏移量 
    app:srlHeaderTriggerRate="0.5">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="?attr/actionBarSize"
        android:clipToPadding="false"
        android:overScrollMode="never"
        app:layout_srlSpinnerStyle="Scale"
        app:layout_srlBackgroundColor="@android:color/transparent"
        />
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="?attr/actionBarSize"
        android:clipToPadding="false"
        android:overScrollMode="never"
        app:layout_srlSpinnerStyle="Scale"
        app:layout_srlBackgroundColor="@android:color/transparent"
        />

    <com.scwang.smartrefresh.layout.footer.ClassicsFooter
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:srlAccentColor="#888"
        app:srlClassicsSpinnerStyle="Translate"
        app:srlDrawableProgress="@drawable/ic_progress_puzzle"/>
</com.scwang.smartrefresh.layout.SmartRefreshLayout>

额外介绍几个控件

// 开源的 gif 动画库
<pl.droidsonroids.gif.GifImageView
    android:id="@+id/gifView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="centerCrop"
    android:src="@mipmap/gif_header_repast"
    app:layout_srlSpinnerStyle="Scale"
    app:layout_srlBackgroundColor="@android:color/transparent"/>

// 磨砂效果 控件
 <com.github.mmin18.widget.RealtimeBlurView
        android:id="@+id/blurView"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:visibility="visible"/>

// 开源的 banner 
<com.youth.banner.Banner
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/convenientBanner"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    app:title_height="25dp"
    app:title_textsize="12sp"
    app:indicator_width="6dp"
    app:indicator_height="6dp"
    app:is_auto_play="true"/>

// 这个是 万能的 RecycleView 适配器
https://github.com/CymChad/BaseRecyclerViewAdapterHelper

//开源的 加载进度条
<ezy.ui.layout.LoadingLayout
        android:id="@+id/loading"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

BottomSheetDialog dialog = new BottomSheetDialog(this);
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
    @Override
    public void onDismiss(DialogInterface dialog) {
        dialog.dismiss();
    }
});
dialog.setContentView(init(LayoutInflater.from(this).inflate(R.layout.activity_scroll2, null, false)));
dialog.setCancelable(false);
dialog.show();

MaterialHeader

仿 google 的下拉刷新

parallax 视差效果

final View parallax = findViewById(R.id.parallax);

refreshLayout.setOnMultiPurposeListener(new SimpleMultiPurposeListener() {
            @Override
            public void onRefresh(@NonNull RefreshLayout refreshLayout) {
                refreshLayout.finishRefresh(3000);
            }

            @Override
            public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
                refreshLayout.finishLoadMore(2000);
            }
            @Override
            public void onHeaderMoving(RefreshHeader header, boolean isDragging, float percent, int offset, int headerHeight, int maxDragHeight) {
                mOffset = offset / 2;
                parallax.setTranslationY(mOffset - mScrollY);
                toolbar.setAlpha(1 - Math.min(percent, 1));
            }
//            @Override
//            public void onHeaderPulling(@NonNull RefreshHeader header, float percent, int offset, int bottomHeight, int maxDragHeight) {
//                mOffset = offset / 2;
//                parallax.setTranslationY(mOffset - mScrollY);
//                toolbar.setAlpha(1 - Math.min(percent, 1));
//            }
//            @Override
//            public void onHeaderReleasing(@NonNull RefreshHeader header, float percent, int offset, int bottomHeight, int maxDragHeight) {
//                mOffset = offset / 2;
//                parallax.setTranslationY(mOffset - mScrollY);
//                toolbar.setAlpha(1 - Math.min(percent, 1));
//            }
        });

TwoLevelHeader

可以使用两层标题

<com.scwang.smartrefresh.layout.header.TwoLevelHeader
    android:id="@+id/header"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <ImageView
        android:id="@+id/second_floor"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@string/app_name"
        android:scaleType="centerCrop"
        android:src="@mipmap/image_second_floor"/>
    <FrameLayout
        android:id="@+id/second_floor_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:alpha="0">
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:contentDescription="@string/app_name"
            android:scaleType="centerCrop"
            android:src="@mipmap/image_second_floor_content"/>
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layoutManager="android.support.v7.widget.LinearLayoutManager"/>
    </FrameLayout>
    <com.scwang.smartrefresh.layout.header.ClassicsHeader
        android:id="@+id/classics"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="?attr/actionBarSize"/>
</com.scwang.smartrefresh.layout.header.TwoLevelHeader>

ClassicsHeader 经典下拉头部

具体可以查看文档

https://github.com/scwang90/SmartRefreshLayout/blob/master/art/md_property.md

//设置时间格式
mClassicsHeader.setTimeFormat(new SimpleDateFormat("更新于 MM-dd HH:mm", Locale.CHINA));
mClassicsHeader.setTimeFormat(new DynamicTimeFormat("更新于 %s"));
//显示时间
mClassicsHeader.setEnableLastTime(true);
//隐藏时间
mClassicsHeader.setEnableLastTime(false);
//背后固定
mClassicsHeader.setSpinnerStyle(SpinnerStyle.FixedBehind);
//尺寸拉伸
mClassicsHeader.setSpinnerStyle(SpinnerStyle.values[1]);
//位置平移
mClassicsHeader.setSpinnerStyle(SpinnerStyle.Translate);
<com.scwang.smartrefresh.layout.header.ClassicsHeader
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:srlPrimaryColor="@color/colorPrimary"  设置主色调
    app:srlAccentColor="@android:color/white"  设置文字等内容色调
    app:srlDrawableProgress="@drawable/animation_loading_frame"/>  设置刷新时的动画 默认为菊花转动

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item  android:duration="300" android:drawable="@drawable/ic_fly_refresh_phone"/>
    <item  android:duration="300" android:drawable="@drawable/ic_fly_refresh_folder"/>
    <item  android:duration="300" android:drawable="@drawable/ic_fly_refresh_poll"/>
    <item  android:duration="300" android:drawable="@drawable/ic_fly_refresh_send"/>
</animation-list>


<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_index_dashboard"/>
</layer-list>

内容不偏移

mRefreshLayout.setEnableHeaderTranslationContent(false);

内容跟随偏移

mRefreshLayout.setEnableHeaderTranslationContent(true);

PathParser SVG 转Path

用Canvas 画 Path

PathParser.createPathFromPathData(svgPath)
public abstract class PaintDrawable extends Drawable {

    protected Paint mPaint = new Paint();

    protected PaintDrawable() {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
        mPaint.setColor(0xffaaaaaa);
    }

    public void setColor(int color) {
        mPaint.setColor(color);
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值