-
基于Lottie的动态锁屏APP
2019-11-11 21:57:04实习期间做的一个项目,项目主要功能是提供动态锁屏壁纸,并实现手势交互,增加可玩性。壁纸资源基于Lottie动画,项目还实现了可以从手机资源库中读取Lottie的json文件,可以从网上下载Lottie资源的json文件,从而可...基于Lottie的动态锁屏APP
先上实现图
锁屏效果图
APP主页面
预览页面
设置解锁方式
主页面侧边栏
实现思路:(大家实现类似APP时,也可以按照这种步骤进行分工,并进行迭代开发)
重点部分:其实在这个APP中,比较难的一点就是锁屏页面自定义View的实现,和锁屏交互逻辑的实现,现在放上这两个部分的代码。锁屏中间部分的自定义View
package com.example.lockscreen.myview; import android.animation.Animator; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextClock; import android.widget.TextView; import com.airbnb.lottie.LottieAnimationView; import com.example.lockscreen.R; import com.example.lockscreen.StyleDetailActivity; import com.example.lockscreen.vo.CodeData; import com.example.lockscreen.vo.ViewDisplay; import com.example.lockscreen.vo.WordDisplay; import androidx.appcompat.app.AppCompatActivity; public class LockScreenView extends RelativeLayout { private ProgressBar mProgressBar; private LottieAnimationView mLottieView; private RelativeLayout.LayoutParams mProgressBarLayout; private CodeData mCodeData; private ViewDisplay mViewDisplay; private Context mContext; //此视图布局 private View mView; //屏幕宽和高 private int mWidth; private int mHeight; //手指按下时的坐标 private float mStartX; private float mStartY; //展示该View的Activity private AppCompatActivity mActivity; //显示进度条 private final float SHOW_PROGRESS = 1f; //-1代表相对于父容器 private final int SUBJECT = -1; //解锁方式 private int mInteractionCode = 1; //正在播放动画的文件名(assets下加载) private String mLottieName; //正在播放动画的json字符串(本地加载) private String mLottieJson; public LockScreenView(Context context) { this(context, null); } public LockScreenView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LockScreenView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } /** * 初始化 * * @param context * @param attrs */ private void init(Context context, AttributeSet attrs) { mView = LayoutInflater.from(context).inflate(R.layout.lock_screen,this, true); mProgressBar = mView.findViewById(R.id.progressBar); mProgressBarLayout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); mProgressBarLayout.addRule(RelativeLayout.CENTER_HORIZONTAL); mProgressBarLayout.addRule(RelativeLayout.ALIGN_BOTTOM, SUBJECT); //加载视图布局 mLottieView = mView.findViewById(R.id.lock_screen_lottie); mLottieView.addAnimatorListener(myLottieListener); mActivity = (AppCompatActivity) context; //mContext = getContext(); mCodeData = new CodeData(); mViewDisplay = new ViewDisplay(mCodeData); } /** * 测量 * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //测量子view measureChildren(widthMeasureSpec, heightMeasureSpec); //获取屏幕宽高 mWidth = getMeasuredWidth(); mHeight = getMeasuredHeight(); } /** * 布局 * * @param changed * @param l * @param t * @param r * @param b */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); //设置滚动条的相对布局位置和相关属性 mProgressBar.setMax(mWidth / 5 * 2); mProgressBarLayout.topMargin = mHeight / 10 * 9; mProgressBarLayout.width = mWidth / 5 * 2; mProgressBarLayout.height = mHeight / 100; mProgressBar.setLayoutParams(mProgressBarLayout); } /** * 动画播放进度监听器 */ private Animator.AnimatorListener myLottieListener = new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { if (mLottieView.getSpeed() < 0) { //用户滑动屏幕后将速度恢复正常 mLottieView.setSpeed(1f); } } }; /** * 此方法会在所有的控件都从xml中加载完毕后加载 */ @Override protected void onFinishInflate() { super.onFinishInflate(); } /** * 监听屏幕点击和移动事件 * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { final float x = event.getRawX(); final float y = event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mStartX = x; mStartY = y; mLottieView.pauseAnimation(); break; case MotionEvent.ACTION_MOVE: setProgress(x, y); break; case MotionEvent.ACTION_UP: // 手指抬起时和异常取消时都是使用同一种方式判断是否解锁 case MotionEvent.ACTION_CANCEL: handlerMoveEvent(x, y); break; } return true; } /** * 根据手指触摸的情况更改进度条的进度和Lottie动画的播放状态 * * @param x */ private void setProgress(float x, float y) { float offset = getOffset(x, y); int progressScale = (int) (mProgressBar.getMax() * offset / mProgressBar.getMax());//进度条进度 float lottieScale = offset / mProgressBar.getMax();//动画播放进度 if (offset > 0) { //设置进度条进度 mProgressBar.setAlpha(SHOW_PROGRESS); mProgressBar.setProgress(progressScale); //设置lottie播放进度 if (lottieScale <= 1) { mLottieView.setProgress(lottieScale); } else { mLottieView.setProgress(1); } } else { mProgressBar.setAlpha(0); } } /** * 根据进度条进度判断是否解锁成功 * * @param */ private void handlerMoveEvent(float x, float y) { if (mProgressBar.getMax() == mProgressBar.getProgress()) { //解锁成功 mActivity.finish(); } else { float offset = getOffset(x, y); if (offset >= 0) { //未解锁 //重置进度条 mProgressBar.setProgress(0); mProgressBar.setAlpha(0); //倒放动画至初始位置 mLottieView.setSpeed(-1f); mLottieView.resumeAnimation(); } } } /** * 通过用户传进的交互码,设置屏幕解锁方式 * @return */ public void setDirection(int code) { mCodeData.setCode(code); } /** * 通过x,y坐标计算出想要的偏移量 * @param x * @param y * @return */ private float getOffset(float x, float y){ float offset = 0; offset = mViewDisplay.offsetShow(mStartX , mStartY , x , y); return offset; } /** * 暴露此接口,用于播放从assets文件夹下加载的动画 * * @param lottieName */ public void setLottieName(String lottieName) { mLottieName = lottieName; mLottieView.setAnimation(mLottieName); invalidate(); } /** * 暴露此接口,用于播放从本地加载的Lottie动画 * * @param lottieJson */ public void setLottieFromJson(String lottieJson) { mLottieJson = lottieJson; mLottieView.setAnimationFromJson(mLottieJson, null); invalidate(); } /** * 暴露此接口,用于获取当前正在播放的动画名(assets下加载时) * * @return */ public String getLottieName() { return mLottieName; } /** * 暴露此接口,用于获取当前正在播放的json字符串(从本地加载时) * @return */ public String getLottieJson() { return mLottieJson; } }
时间和提示文字部分的自定义View
package com.example.lockscreen.myview; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Shader; import android.util.AttributeSet; import androidx.appcompat.widget.AppCompatTextView; public class LockScreenTextView extends AppCompatTextView { private int mViewWidth; private LinearGradient mLinearGradient; private Paint mPaint; private Matrix mGradientMatrix; private int mTranslate; public LockScreenTextView(Context context) { this(context, null); } public LockScreenTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LockScreenTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * 测量View * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mViewWidth = getMeasuredWidth(); } /** * 先在onSizeChanged()方法中进行一些初始化工作,根据View的宽来设置一个LinearGradient渐变渲染器 * 此方法在onMeasure()方法调用完成后调用 * * @param w * @param h * @param oldw * @param oldh */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mViewWidth > 0) { //获取当前绘制TextView的Paint对象 mPaint = getPaint(); //自定义渐变渲染器 mLinearGradient = new LinearGradient( 0, 0, mViewWidth, 0, //渲染的起止坐标 new int[]{Color.BLACK, Color.WHITE, Color.BLACK},//定义渐变颜色 new float[]{0.25F, 0.5F, 1.0F},//不同渲染渲染阶段渲染不同的颜色 Shader.TileMode.CLAMP);//Shader渲染器 //为画笔设置渐变渲染器 mPaint.setShader(mLinearGradient); //创建一个单位矩阵 mGradientMatrix = new Matrix(); } } /** * 在onDraw方法中,通过矩阵的方法来不断平移渐变效果,使绘制文字时产生动态的闪动效果 * * @param canvas */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mLinearGradient != null) { //每次向右平移字体长度的二十分之一 mTranslate += mViewWidth / 20; if (mTranslate > 2 * mViewWidth) { mTranslate = -mViewWidth; } //设置单位矩阵平移 mGradientMatrix.setTranslate(mTranslate, 0); //为着色器设置矩阵 mLinearGradient.setLocalMatrix(mGradientMatrix); //循环等待时间 postInvalidateDelayed(100); } } }
部分手势交互的处理逻辑代码
/* * 按压解锁 */ package com.example.lockscreen.myview; import android.content.Context; import android.content.res.AssetManager; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.RelativeLayout; import androidx.appcompat.app.AppCompatActivity; import com.airbnb.lottie.LottieAnimationView; import com.example.lockscreen.R; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Calendar; public class MarkView extends RelativeLayout { private LottieAnimationView mLottieView; //上下文 private Context mContext; private Calendar mCalendar; //此视图布局 private View mView; //json文件名 private String mFilename; //展示该View的Activity private AppCompatActivity mActivity; //json字符串 private String mLottieJson; //手指按压开始的时间 private long mStartTime; //手指按压结束的时间 private long mEndTime; //Lottie图层的时长 private double mDuration; //图层结束的帧数 private int mEndFrame; //图层开始的帧数 private int mBeginFrame; //设置Lottie的起始帧 private int mLottieStartFrame; //Lottie的帧率 private double mFrameRate; //毫秒数 private int mMilliSecond; //BaseView类 private BaseView mBaseView; public MarkView(Context context) { this(context, null); } public MarkView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MarkView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, AttributeSet attrs) { //加载视图布局 mView = LayoutInflater.from(context).inflate(R.layout.lock_screen_interaction, this, true); mLottieView = mView.findViewById(R.id.lottie_interaction); mBaseView = new BaseView(); mFilename = getResources().getString(R.string.mark_lottie); mLottieView.setAnimation(mFilename); mLottieStartFrame = getResources().getInteger(R.integer.mark_lottieStartFrame); mLottieView.setMinFrame(mLottieStartFrame); mContext = getContext(); mLottieJson = mBaseView.getJson(mFilename,mContext); datafromJson(mLottieJson); mMilliSecond = getResources().getInteger(R.integer.mark_milliSecond); mDuration = ((mEndFrame - mBeginFrame) / (mFrameRate)) * mMilliSecond; mActivity = (AppCompatActivity)context; } public boolean onTouchEvent(MotionEvent event) { final float x = event.getRawX(); final float y = event.getRawY(); // 手指位置与圆点之间的距离 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLottieView.setSpeed(1f); mCalendar = Calendar.getInstance(); mStartTime = mCalendar.getTimeInMillis(); break; case MotionEvent.ACTION_MOVE: setProgress(); break; case MotionEvent.ACTION_UP: // 手指抬起时和异常取消时都是使用同一种方式判断是否解锁 case MotionEvent.ACTION_CANCEL: handlerMoveEvent(); break; } return true; } /* *Lottie播放帧数的设置 */ private void setProgress() { mCalendar = Calendar.getInstance(); mEndTime = mCalendar.getTimeInMillis(); //lottie播放比例 float playrate; playrate = (float) ((mEndTime - mStartTime) / mDuration); mLottieView.setProgress(playrate); } /* * 手指抬起事件 */ private void handlerMoveEvent() { //没达到阈值就回放 if(mLottieView.getFrame() < mEndFrame - 7){ if(mLottieView.getFrame() > mLottieStartFrame + 2){//给于误触,简单的点击不响应 mLottieView.setSpeed(-1f); mLottieView.resumeAnimation(); } }else{ mActivity.finish(); } } /* * 从json文件中获取数据 */ private void datafromJson(String lottiejson) { try { //创建一个包含原始json串的json JSONObject dataJson = new JSONObject(lottiejson); // 读取对象里的字段值 mBeginFrame = dataJson.getInt("ip"); mEndFrame = (int) dataJson.getDouble("op"); mFrameRate = dataJson.getDouble("fr"); } catch (JSONException e) { e.printStackTrace(); } } }
/* * 拖动解锁 */ package com.example.lockscreen.myview; import android.animation.Animator; import android.content.Context; import android.content.res.AssetManager; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.RelativeLayout; import androidx.appcompat.app.AppCompatActivity; import com.airbnb.lottie.LottieAnimationView; import com.example.lockscreen.R; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class BoxView extends RelativeLayout { private LottieAnimationView mLottieView; //上下文 private Context mContext; //此视图布局 private View mView; //手指按下时的坐标 private float mStartX; private float mStartY; //屏幕宽和高 private int mWidth; private int mHeight; //json文件名 private String mFilename; //展示该View的Activity private AppCompatActivity mActivity; //拉绳子图层的结束帧 private int mEndFrame; //拉绳子的上边界 private float mRopeTop; //拉绳子的下边界 private float mRopeButtom; //触碰到绳子的标志 private boolean mTouchFlag; //在绳子上滑动的标志 private boolean mSlipFlag; //json字符串 private String mLottieJson; //BaseView类 private BaseView mBaseView; public BoxView(Context context) { this(context, null); } public BoxView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public BoxView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, AttributeSet attrs) { //加载视图布局 mView = LayoutInflater.from(context).inflate(R.layout.lock_screen_interaction, this, true); mLottieView = mView.findViewById(R.id.lottie_interaction); mBaseView = new BaseView(); mFilename = getResources().getString(R.string.giftbox_lottie); mContext = getContext(); mLottieView.setAnimation(mFilename); mLottieJson = mBaseView.getJson(mFilename,mContext); datafromJson(mLottieJson); mActivity = (AppCompatActivity)context; } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //获取屏幕宽高 mWidth = getWidth(); mHeight = getHeight(); mRopeTop = mHeight / 20 * 11; mRopeButtom = mHeight / 13 * 8; } /* * 从json文件中获取数据 */ private void datafromJson(String lottiejson) { try { //创建一个包含原始json串的json JSONObject dataJson = new JSONObject(lottiejson); // 找到layers的json数组 JSONArray layers = dataJson.getJSONArray("layers"); // 获取layers数组的第1个json对象 JSONObject info1 = layers.getJSONObject(1); // 读取info1对象里的op字段值 int op = (int)info1.getDouble("op"); //比结束帧稍小一点,手指离开时可以有缓冲播放 mEndFrame = op - 5; } catch (JSONException e) { e.printStackTrace(); } } public boolean onTouchEvent(MotionEvent event) { final float x = event.getRawX(); final float y = event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mStartX = x; mStartY = y; if(mStartY >= mRopeTop && mStartY <= mRopeButtom ){ mTouchFlag = true; } break; case MotionEvent.ACTION_MOVE: if(y >= mRopeTop && y <= mRopeButtom) { mSlipFlag = true; }else{ mSlipFlag = false; } setProgress(x); break; case MotionEvent.ACTION_UP: // 手指抬起时和异常取消时都是使用同一种方式判断是否解锁 case MotionEvent.ACTION_CANCEL: handlerMoveEvent(); break; } return true; } /* * 计算滑动时Lottie播放的对应帧数 */ private void setProgress(float x) { if(mTouchFlag){ if(mSlipFlag){//手指在绳子附近滑动 if(x - mStartX > 20){//设置误触距离,防止误触和向左移动 //手指滑动的距离 float length; length = x - mStartX; //Lottie播放比例 float playRate; playRate = length / (mWidth / 2); //当前播放的帧数 int frame; frame = (int) (mEndFrame * playRate); //防止实时移动的帧大于最大值 if(frame > mEndFrame){ frame = mEndFrame; } mLottieView.setFrame(frame); } }else{//手指不在绳子附近滑动 mLottieView.setSpeed(-0.5f); mLottieView.resumeAnimation(); mTouchFlag = false; } } } /* * 手指抬起事件 */ private void handlerMoveEvent() { if(mTouchFlag && mSlipFlag){ //还没到达阈值时,让Lottie倒放 if(mLottieView.getFrame() < mEndFrame){//设置误触距离 if(mLottieView.getFrame() > 3){ mLottieView.setSpeed(-0.5f); mLottieView.resumeAnimation(); } }else{ mLottieView.setSpeed(0.1f); mLottieView.resumeAnimation(); mLottieView.addAnimatorListener(myLottieListener); } } mTouchFlag = false; mSlipFlag = false; } /** * 动画播放进度监听器 */ private Animator.AnimatorListener myLottieListener = new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { mActivity.finish(); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }; }
/* * 绘制图形解锁 */ package com.example.lockscreen.myview; import android.animation.Animator; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.RelativeLayout; import androidx.appcompat.app.AppCompatActivity; import com.airbnb.lottie.LottieAnimationView; import com.example.lockscreen.R; public class CheckView extends RelativeLayout { private LottieAnimationView mLottieView; //此视图布局 private View mView; //手指按下时的坐标 private float mStartX; private float mStartY; //屏幕宽和高 private int mWidth; private int mHeight; //json文件名 private String mFilename; //画勾的第一个点的坐标 private float mFirstX; private float mFirstY; //画勾的第二个点的坐标 private float mSecondX; private float mSecondY; //画勾的第三个点的坐标 private float mThirdX; private float mThirdY; //点击次数 private int mTemp; //json字符串1 private String mLottieJson1; //json字符串2 private String mLottieJson2; //json字符串3 private String mLottieJson3; //json字符串4 private String mLottieJson4; //json字符串5 private String mLottieJson5; //json字符串6 private String mLottieJson6; //json字符串7 private String mLottieJson7; //拼接而成的json的字符串 private String mAppendLottieJson; //展示该View的Activity private AppCompatActivity mActivity; //是否点击屏幕的标志 private boolean mTouchFlag; //点击区域的上边界 private float mFieldTop; //点击区域的下边界 private float mFieldButtom; public CheckView(Context context) { this(context, null); } public CheckView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CheckView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, AttributeSet attrs) { //加载视图布局 mView = LayoutInflater.from(context).inflate(R.layout.lock_screen_interaction, this, true); mLottieView = mView.findViewById(R.id.lottie_interaction); mFilename = getResources().getString(R.string.check_lottie); mLottieView.setAnimation(mFilename); mLottieView.playAnimation(); mLottieView.addAnimatorListener(myLottieListener); mActivity = (AppCompatActivity)context; mLottieJson1 = getResources().getString(R.string.check_lottiejson1); mLottieJson3 = getResources().getString(R.string.check_lottiejson3); mLottieJson5 = getResources().getString(R.string.check_lottiejson5); mLottieJson7 = getResources().getString(R.string.check_lottiejson7); } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //获取屏幕宽高 mWidth = getWidth(); mHeight = getHeight(); mFieldTop = mHeight / 7 * 2; mFieldButtom = mHeight / 9 * 7; } public boolean onTouchEvent(MotionEvent event) { final float x = event.getRawX(); final float y = event.getRawY(); // 手指位置与圆点之间的距离 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mStartX = x; mStartY = y; if(mStartY >= mFieldTop && mStartY <= mFieldButtom){ mLottieView.setFrame(0); mLottieView.pauseAnimation(); mTemp++; setLocation(mTemp); } break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: // 手指抬起时和异常取消时都是使用同一种方式判断是否解锁 case MotionEvent.ACTION_CANCEL: handlerMoveEvent(); break; } return true; } /* * 设置画勾的三个点的坐标 */ private void setLocation(int temp){ switch(temp){ case 1: mFirstX = changeX(mStartX); mFirstY = changeY(mStartY); mLottieJson2 = "" + mFirstX + "," + mFirstY; break; case 2: mSecondX = changeX(mStartX); mSecondY = changeY(mStartY); mLottieJson4 = "" + mSecondX + "," + mSecondY; break; case 3: mThirdX = changeX(mStartX); mThirdY = changeY(mStartY); mLottieJson6 = "" + mThirdX + "," +mThirdY; break; } } /* * 手指抬起事件 */ private void handlerMoveEvent() { if(mTemp == 3){ mAppendLottieJson = mLottieJson1 + mLottieJson2 + mLottieJson3 + mLottieJson4 + mLottieJson5 + mLottieJson6 + mLottieJson7; mLottieView.setAnimationFromJson(mAppendLottieJson,null); mLottieView.playAnimation(); mTouchFlag = true; mTemp = 0; } } /* *将手机坐标系的左边转化为Lottie坐标系的坐标 */ private float changeX(float x){ float lottieX; lottieX = (x - mWidth / 2) / 10; return lottieX; } private float changeY(float y){ float lottieY; lottieY = (y - (mHeight / 2 - mHeight / 15)) / 10; return lottieY; } /** * 动画播放进度监听器 */ private Animator.AnimatorListener myLottieListener = new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if(mTouchFlag){ mTouchFlag = false; mActivity.finish(); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }; }
说明:
1.以上就是对于这个项目关键部分的说明,大家可以参考这种思路进行开发;
2.在项目中还进行了一些优化,比如运用RecyclerView的缓存机制和Flutter提供的预加载和缓存机制减少页面中加载Lottie动画时出现的卡顿问题等等。
3.这个项目也还有很多需要完善的地方,欢迎大佬们提出意见,也希望这篇博客对大家有所帮助,谢谢。项目的源码可以到此查看
https://github.com/Zero-zhangzehua/LockScreen -
苹果手机输入屏保后锁屏_苹果手机动态锁屏屏保怎么设置
2020-12-29 08:40:101、首先2113需要找到一个好看的动态live图片,这里5261建4102议下载“火萤”app,这里面有很多动态图片可以直接下载1653。2、打开火萤app,点击【发现】,搜索自己喜欢的壁纸类型。3、打开喜欢的壁纸,点击【下载】...1、首先2113需要找到一个好看的动态live图片,这里5261建4102议下载“火萤”app,这里面有很多动态图片可以直接下载1653。2、打开火萤app,点击【发现】,搜索自己喜欢的壁纸类型。3、打开喜欢的壁纸,点击【下载】,就可以自动存入手机相册了。4、在手机桌面点击【照片】。5、找到想要设置的动图,点击图片。6、点击左下角的图标。7、在弹出的选项栏中找到并点击【用作墙纸】。8、最后点击【设定】并选择【设定锁屏壁纸】即可。本回答被网友采纳1.首先找到手2113机页面上的设置,点击打开5261。2.在设置页面上找到墙纸,点击进4102入墙纸页1653面。3.进入墙纸后,点击选择新的墙纸。4
现在越来越多的人喜欢设置动态锁屏,比起静态锁屏确实看起来更生动形象,今天我们就来介绍一下苹果手机怎么设置动态锁屏;
在iOS8系统中,时钟包括了世界时钟、闹钟、秒表、计时器四个独立功能,而这些功能都有丰富的使用功能 1.图标 时钟应用的图标在iOS7时代变成了动态的,可以实时显示具体时间。而到了iOS8,苹果进一步将图标的指针加粗,使其更加易于识别。 时钟 2
首先打开手机桌面上的动态壁纸App,进入页面后点击下方工具栏里的【发现】,手动下滑找到自己喜欢的动态壁纸;
需要手机支持动态壁纸功能,之后选择一个动态的壁纸就可以使用了,以苹果11为例,设置的方法如下: 1、首先用户选择手机中的设置。 2、找到墙纸选项。 3、找到选取新墙纸。 4、在里面找到动态墙纸。 5、选择一个动态墙纸。 6、选择设定。 7、选
点击右下角下载的图标,等待下载,下载完成后返回到手机主界面,打开【照片】;
1、打开“设置” 2、点击“通用” 3、打开“自动锁定” 4、重新回到“设置” 5、点击“壁纸” 6、选取新的壁纸 7、选取照片 8、设定为锁屏
找到【最近项目】这个相册,点击下载好的动态壁纸,然后选择左下角分享的图标;
1、打开“设置” 2、点击“通用” 3、打开“自动锁定” 4、重新回到“设置” 5、点击“壁纸” 6、选取新的壁纸 7、选取照片 8、设定为锁屏
手动下滑,点击【用作墙纸】,再选择右下角的【设定】,在弹出的窗口中选择【设定锁定屏幕】即可。
打开手机系统主菜单,找到【系统设置】图标,点击进入。 在【系统设置】设置中找到【显示及传感器设置】选项,点击进入。 从这里可以选择设置墙纸、屏幕亮度、及其各种传感器。 点击【墙纸设置】,进入墙纸设置页面,设置桌面、锁屏墙纸及通讯录
iphone设置锁2113屏动态壁纸和主屏动态的方法:5261步骤41021:在您的设备上启动设1653置应用。步骤2:点击壁纸。步骤3:点击选择新壁纸。步骤4:你可以看到三类壁纸:选择动态壁纸步骤5:现在点按要显示的任何动态壁纸。(请注意,当您点击任何动态壁纸时,会选择“实时照片”选项。如果选择静止或透视,则无法获得动态壁纸的效果。)步骤6:点击设置,菜单将向上滑动,有三个选项:设置锁定屏幕,设置主屏幕和设置两者。步骤7.点击三个选项中的任何一个,壁纸将相应地设置。设置好后,返回系统照片或再次锁屏即可看到动态壁纸。本回答被网友采纳1、点击打开“设置”2113选项,找到“墙纸与亮度”5261,
-
iphone主屏幕动态壁纸_灵动优美的iphone高清动态锁屏壁纸
2021-01-04 16:58:55看惯了千篇一律的静态壁纸,厌烦了自带的动态壁纸,不妨来试试小编给大家精心挑选的几款Live Photos,除了惊艳还是惊艳^_^先来几个样张:大家不要直接将这两张图用作设置为锁屏壁纸,这样就没有原来的效果了,下面小...看惯了千篇一律的静态壁纸,厌烦了自带的动态壁纸,不妨来试试小编给大家精心挑选的几款Live Photos,除了惊艳还是惊艳^_^
先来几个样张:
大家不要直接将这两张图用作设置为锁屏壁纸,这样就没有原来的效果了,下面小编来介绍大家怎么设置Live Photos。
锁屏Live Photos壁纸获取及设置方法:
1、关注公众号“小熊资讯宝"回复数字“85”获取壁纸下载地址;
2、将页面用Safari浏览器打开后会跳转至微博;
3、在微博推文里挑选出你喜欢的壁纸并点击壁纸,待壁纸加载完成后保存至照片;
4、进入照片,单击壁纸,将壁纸设定为锁定屏幕,进入锁屏页面后只需长按壁纸即可动起来。
ps:大家记得设置的是锁定屏幕,设置主屏幕是没有效果的,不过小编倒是很期待iphone可以自定义设施动态锁屏壁纸,自带的动态墙纸实在太丑了^_^
老铁,顺带打卡!支持下
绅士每日打卡(0/1)
每天只需要完成一次任务,千万不要重复完成
↓↓【打卡区感谢有你】↓↓
-
esxi6.7密码设置规则_如何为手机设置动态锁屏密码
2020-12-22 15:14:13我们知道任何安卓系统的手机都可以设置锁屏密码,但是解锁手机时密码很容易被别人偷...该软件的下载链接为:https://pan.baidu.com/s/1jK26aaLHG8cdGQ-hIcFSMg 提取码:ghdf用这款软件设置的密码是动态,是一直都...我们知道任何安卓系统的手机都可以设置锁屏密码,但是解锁手机时密码很容易被别人偷看到而失去保护作用。今天给大家分享一种为手机设置动态密码的方法。
要为手机设置动态密码,首先需要为手机安装一款app,名称叫"时间锁屏",软件只有9M大小。该软件的下载链接为:https://pan.baidu.com/s/1jK26aaLHG8cdGQ-hIcFSMg 提取码:ghdf
用这款软件设置的密码是动态,是一直都在变化的密码,即是通过将系统时间作为手机的锁屏密码。
下载安装“时间锁屏”后,打开就可为手机设置动态的密码了。操作步骤如下图所示:
如果你的手机是24小时格式,要在24小时格式后面打上勾,设置好后,密码是多少?比如14时53分,解锁手机的动态密码就是1453。如此设置的密码,不同时间密码是不一样的,即锁屏密码是动态的。
还可以只设置分钟的动态密码,前两位可以随机设置,比如设置为8653.,这里的86是自主设置的数字密码,53为53分钟的意思。不同时间段的分钟数字密码不一样,再也不怕别人知道你自己的密码了。
另外,如果的觉得密码太简单,还可以加密成更复杂的密码,可以去体验一下,设置成只有知道时间规则的你才可以解锁手机的密码,打造自己安全好记的密码体系。
以上是今天分享的为手机设置动态密码的方法。欢迎转发、收藏和评论。多谢阅读、关注。
-
仿iphone动态萤火虫锁屏应用源码
2015-02-06 09:33:38该源码是仿iphone动态萤火虫锁屏应用源码,源码SkyLock,这也是最近弄了一款锁屏,苦于市场百般阻拦与锁屏应用数量实在太多,于是将它拿出来开源;废话不多说,希望大家能够希望,更多说明请看下面的吧。 详细... -
华为自带时钟天气下载_人气火爆的时钟轮盘锁屏,小米、Redmi机型设置教程三步曲...
2021-01-09 21:50:50最近DY里流行一款纯文字的罗盘转动时钟,非常酷炫,鉴于米粉朋友的多次需求,本期就跟大家...■设置MIUI自带动态壁纸多次测试,直接把时钟壁纸,即使在插件内选择了设为锁屏与桌面,MIUI10系统依然只有桌面有效果,... -
如何设置电脑自动锁屏_这个手机锁屏密码竟可以根据时间而变化!密码每分钟都会发生改变...
2020-11-07 18:54:47如今,手机已经成为了我们生活中不可或缺的一部分。...下面小编教大家设置一个动态的锁屏密码,会随着时间的变化而产生解锁变化,这样别人就不知道怎么解开了。这里我们先下载时间解锁APP,不过注... -
mysql命令桌面壁纸_【upupoo动态桌面壁纸和mysql-jdbc.jar哪个好用】upupoo动态桌面壁纸和mysql-jdbc.jar...
2021-01-19 02:56:37软件功能海量高清动态壁纸,优质动态壁纸、动态锁屏一网打尽丰富壁纸分类,多样化风格设计,千万壁纸挑你喜欢应用内一键设置壁纸炫酷3D、粒子动感特效,畅享全新视觉冲击一流设计师,精良力作每日更新趣味交互特效,... -
python时钟罗盘酷炫代码_人气火爆的时钟轮盘锁屏,小米、Redmi机型设置教程三步曲...
2020-12-04 13:24:45最近DY里流行一款纯文字的罗盘转动时钟,非常酷炫,鉴于米粉朋友的多次需求,本期就跟大家...■设置MIUI自带动态壁纸多次测试,直接把时钟壁纸,即使在插件内选择了设为锁屏与桌面,MIUI10系统依然只有桌面有效果,... -
八卦动态时钟html代码_抖音很火的八卦时钟轮盘动态壁纸
2021-01-10 20:12:13最近在抖音上有一款“时间转盘文字壁纸”非常火,炫酷的锁屏动态效果,俘获了不少网友的小心肝。随后小米、华为等手机的主题商店都上架了这款壁纸。但是无一例外的都是收费的!不少网友在网络上苦苦寻找这款“时间... -
华为主题包hwt下载_华为主题 | 名画
2021-01-03 08:18:38名画01前言每周更新一次,没办法量产很抱歉因为还要上学,请谅解~但...禁止二改 禁止二传 禁人身攻击感谢你们的理解和陪伴~02设计锁屏动态气泡 ,封面的小宝宝也很可爱啦~PS:本来想使用梵高星空的动态锁屏,但是像... -
编程制作动态壁纸的思路_一键制作网红爱心充电壁纸!送爱奇艺会员~
2021-01-01 07:41:12激萌君关注我,你会比别人好看。壁纸/头像/表情/玩机▼~ ❤️文末有福利❤️~最近什么壁纸最火?...没关系图文教程同样精彩01扫码下载微视频壁纸APP02底部菜单栏选择 DIY03在热门动态模板右侧选择-更... -
android设置主题背景为壁纸_主题壁纸美化app下载-主题壁纸美化安卓版(DIY定制) - 超好玩...
2021-01-15 19:59:05主题壁纸美化是一款非常实用的手机壁纸软件,海量图片素材可以包揽你的桌面、头像、背景图等等,主题壁纸美化安卓版(DIY定制)还有特殊的透明壁纸,可以设置个性挂件,让你的桌面动起来,感兴趣的小伙伴快来下载主题... -
华为主题包hwt下载_华为主题 | 不二家
2021-01-03 08:18:26不二家1前言每周更新一次,没办法量产很抱歉但每一个主题都很精致这个主题的底包是使用我师傅的,所以里边会有签名不是未经...禁止二改 禁止二传 禁人身攻击感谢你们的理解和陪伴~2设计锁屏新增动态气泡和彩虹充电微... -
华为主题包hwt下载_华为主题 | 万圣节猪蹄
2021-01-03 08:18:31万圣节主题前言每周更新一次,没办法量产很抱歉但每一个主题都很精致这个主题的底包是使用我师傅...禁止二改 禁止二传 禁人身攻击感谢你们的理解和陪伴~设计锁屏新增动态幽灵微信的红包和‘我’的页面都进行了改动时... -
它使用了最新的前端技术栈,内置了i18国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。最大程度上帮助个人,企业节省时间成本和费用...
-
它使用了最新的前端技术栈,内置了i18国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。最大程度上帮助个人,企业节省时间成本和费用...
-
EasyAndroid 包含各种工具类的集合,会不定期更新,欢迎贡献code 使用方法: 2.0.0以后,放弃了support库,请使用AndroidX ... 然后,在自己的Application中调用Utils.init(this);进行初始化 添加混淆: ...
-
新增网络等异常自动处理流程:自动设置永不锁屏;自动处理进程通信的问题;网络异常的时候自动关闭VPN(防止VPN有效性过期),并且自动断开Wi-Fi,再次重新连接(防止IP有效期超时);重启sb、在sb 进行搭建操作界面...
-
OPPO手机主题壁纸提取利器:OPPOThemeWallpaperExtract
2017-08-29 19:38:32OPPOThemeWallpaperExtract是一款用来提取...2、锁屏属于交互类模块,所以锁屏壁纸同其他图片(如时间数字图片、动态元件等)打包在ZIP压缩文件里,所以提取完成后还需要自己解压缩,后续版本会自动解压缩,敬请期待。 -
电脑屏保在哪里设置_八卦时间罗盘屏保 分享
2020-11-18 07:02:51最近抖音上很火的一款...部分机型无法设置锁屏的方法:手机先任意设置一张动态壁纸或动态屏保,然后再进行设置时钟屏保就应该可以显示屏保了;或者更改一下手机锁屏的默认样式试试。安装包下载:https://pan.baid... -
总机云服务Android客户端-卞勋锋.pptx
2020-11-16 13:47:53总机云服务 --联系人公告及应用扩展;2;设计目的和意义;发展现状;应用领域;1登录模块登录验证以及数据的下载和处理 2公告模块...6同事圈模块发动态以及展示同事圈动态 7设置模块包括数据更新版本升级清理缓存锁屏弹屏反 -
整人远控2016 v7.5免费版.rar
2019-07-11 06:33:46软件功能强大,无需设置端口映射,就能实现flash特效整人、吓人、锁屏、自定义信息、打开网页 剪辑版操作。远程管理功能:窗口、进程、服务、远程文件管理等诸多功能。如果对这方面有需求的可以下载体验下。 整人... -
基于iView的Vue 2.0管理系统模板
2019-01-03 14:58:26如果你只是想要一个清醒爽朗的界面,你那下载可以简化版模板来做开发。 功能 登录/登出 权限管理 列表过滤 权限切换 多语言切换 组件 富文本编辑器 降价编辑器 城市级联 图片预览编辑 可拖拽列表 文件上传 数字渐变 ... -
Android代码-APlayer
2019-08-08 01:58:31锁屏控制,可选择原生或者软件实现 心情可生成海报分享 日夜间模式切换,动态改变主题颜色 监测媒体库变化自动刷新,并且可以手动扫描指定目录 其他必备和便捷操作如歌曲信息编辑、睡眠定时、均衡器等 感谢 RxJava ... -
vc++ 应用源码包_8
2012-09-16 11:13:44vc++动态链接库编程之DLL典型实例源代码下载.rar vc++动态链接库编程深入浅出.rar VC++仿Dreamweaver取色器源代码.rar VC++挂机锁屏系统源程序.7z VC++建立桌面或开始菜单快捷方式.rar VC++界面库编程.zip VC++精仿... -
移动安全测试框架MobSF.zip
2019-07-17 05:45:46配置动态分析器我们需要获取以下4个方面的信息,(1)VM UUID(2)快照 UUID(3)主机/代理 IP(4)VM/设备 IP操作步骤1、打开VirtualBox(本文主要以VirtualBox为样例),选择文件->导入应用,并选中MobSF_VM_X.... -
蕊蕊定时器 v2.1.zip
2019-07-14 07:35:54别担心,我们有内置PPTCD版的使用说明书,并且我们将会采用图片文字组合的动态使用说明书,让我们告别纯文字说明书把? 蕊蕊定时器更新: 【新增】鼠标停止开始计时的模式,每次鼠标移动完就自动重新计时 【优化... -
vc++ 应用源码包_1
2012-09-15 14:22:12vc++动态链接库编程之DLL典型实例源代码下载 VC++仿Dreamweaver取色器源代码 VC++挂机锁屏系统源程序 VC++建立桌面或开始菜单快捷方式 VC++界面库编程 SkinMagic 2.21 动态库版本的使用和 Skin++动态库及静态库版本...