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;
}
}