前言:
看著自己簡書創(chuàng)建時間2016年,CSDN2015年虑鼎。自己在這最好的年華居然連技術(shù)文章或者論文都沒發(fā)表過伟端,怎么對的起我看過前輩們的心血和付出杠娱。在此我還是決定了今后的總結(jié)方向不在是單一的筆記和書本廷区,還是為IT大軍做一份貢獻嬉挡。
正文:
寫這篇文章主要是為了當前日益增多三方庫和開發(fā)中的一些日常造輪子胚迫,加自己的經(jīng)驗總結(jié)
效果圖:
庫:
SmartRefreshLayout
Lottie
LottieFiles
布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/sfl"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.xxx.lottie.DesginLottieHeadRefresh
android:id="@+id/headRefresh"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:gravity="center"
android:text="測試代碼"
android:layout_height="wrap_content" />
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
</LinearLayout>
簡單的布局和自定義的HeadRefresh
DesginLottieHeadRefresh:
public class DesginLottieHeadRefresh extends ViewGroup implements RefreshHeader {
private LottieAnimationView lav;
private String asset_loading_json = "desgin/newAnimation.json";
//中心點
private int mCircleDiameter;
@VisibleForTesting
private static final int CIRCLE_DIAMETER = 160;
private RefreshState mState;
public DesginLottieHeadRefresh(Context context) {
this(context, null);
}
public DesginLottieHeadRefresh(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (getChildCount() == 0) {
return;
}
final int width = getMeasuredWidth();
int lottieWidth = lav.getMeasuredWidth();
int lottieHeight = lav.getMeasuredHeight();
int leftLav = width / 2 - lottieWidth / 2;
int topLav = 0;
lav.layout(leftLav, topLav, leftLav + lottieWidth, topLav + lottieHeight / 2);
}
private void initView(Context context) {
final DisplayMetrics metrics = getResources().getDisplayMetrics();
mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density);
lav = new LottieAnimationView(context);
lav.setAnimation(asset_loading_json);
lav.loop(true);
addView(lav);
}
@NonNull
@Override
public View getView() {
return this;
}
@NonNull
@Override
public SpinnerStyle getSpinnerStyle() {
return SpinnerStyle.MatchLayout;
}
@Override
public void setPrimaryColors(int... colors) {
}
@Override
public void onInitialized(@NonNull RefreshKernel kernel, int height, int extendHeight) {
}
@Override
public void onMoving(boolean isDragging, float percent, int offset, int height, int extendHeight) {
}
@Override
public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int extendHeight) {
}
@Override
public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int extendHeight) {
lav.playAnimation();
}
@Override
public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) {
// lav.clearAnimation();
if (lav != null) {
lav.cancelAnimation();
lav.clearAnimation();
}
return 0;
}
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
}
@Override
public boolean isSupportHorizontalDrag() {
return false;
}
@Override
public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
mState = newState;
switch (newState) {
case None:
lav.setFrame(0);
lav.setProgress(0);
break;
case PullDownToRefresh:
lav.setVisibility(View.VISIBLE);
break;
case PullDownCanceled:
break;
case ReleaseToRefresh:
lav.setVisibility(View.VISIBLE);
break;
case Refreshing:
break;
case RefreshFinish:
lav.setVisibility(View.GONE);
break;
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
// canvas.save();
// lav.draw(canvas);
// canvas.restore();
}
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
super.invalidateDrawable(drawable);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getSize(widthMeasureSpec), getSize(heightMeasureSpec));
lav.measure(MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY));
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
}
}
實現(xiàn)步驟:
1.implements RefreshHead View或者ViewGroup
2.實現(xiàn) onMeasure-onLayout 或者實現(xiàn)onDraw(Canvas canvas)
3.Lottiview 加載assets目錄下面 .json動畫完成初步顯示
4.根據(jù)Lottie和SmartRefreshLayout api 完成動畫連貫和狀態(tài)更新
RefreshHead > RefreshInternal
/**
* 獲取實體視圖
* @return 實體視圖
*/
@NonNull
View getView();
/**
* 獲取變換方式 {@link SpinnerStyle} 必須返回 非空
* @return 變換方式
*/
@NonNull
SpinnerStyle getSpinnerStyle();
/**
* 設(shè)置主題顏色
* @param colors 對應(yīng)Xml中配置的 srlPrimaryColor srlAccentColor
*/
void setPrimaryColors(@ColorInt int... colors);
/**
* 尺寸定義完成 (如果高度不改變(代碼修改:setHeader)喷户,只調(diào)用一次, 在RefreshLayout#onMeasure中調(diào)用)
* @param kernel RefreshKernel
* @param height HeaderHeight or FooterHeight
* @param extendHeight extendHeaderHeight or extendFooterHeight
*/
void onInitialized(@NonNull RefreshKernel kernel, int height, int extendHeight);
/**
* 手指拖動下拉(會連續(xù)多次調(diào)用)
* @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+extendHeight) / footerHeight )
* @param offset 下拉的像素偏移量 0 - offset - (footerHeight+extendHeight)
* @param height 高度 HeaderHeight or FooterHeight
* @param extendHeight 擴展高度 extendHeaderHeight or extendFooterHeight
*/
void onPulling(float percent, int offset, int height, int extendHeight);
/**
* 手指釋放之后的持續(xù)動畫(會連續(xù)多次調(diào)用)
* @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+extendHeight) / footerHeight )
* @param offset 下拉的像素偏移量 0 - offset - (footerHeight+extendHeight)
* @param height 高度 HeaderHeight or FooterHeight
* @param extendHeight 擴展高度 extendHeaderHeight or extendFooterHeight
*/
void onReleasing(float percent, int offset, int height, int extendHeight);
/**
* 釋放時刻(調(diào)用一次,將會觸發(fā)加載)
* @param refreshLayout RefreshLayout
* @param height 高度 HeaderHeight or FooterHeight
* @param extendHeight 擴展高度 extendHeaderHeight or extendFooterHeight
*/
void onReleased(RefreshLayout refreshLayout, int height, int extendHeight);
/**
* 開始動畫
* @param refreshLayout RefreshLayout
* @param height HeaderHeight or FooterHeight
* @param extendHeight extendHeaderHeight or extendFooterHeight
*/
void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int extendHeight);
/**
* 動畫結(jié)束
* @param refreshLayout RefreshLayout
* @param success 數(shù)據(jù)是否成功刷新或加載
* @return 完成動畫所需時間 如果返回 Integer.MAX_VALUE 將取消本次完成事件访锻,繼續(xù)保持原有狀態(tài)
*/
int onFinish(@NonNull RefreshLayout refreshLayout, boolean success);
/**
* 水平方向的拖動
* @param percentX 下拉時褪尝,手指水平坐標對屏幕的占比(0 - percentX - 1)
* @param offsetX 下拉時,手指水平坐標對屏幕的偏移(0 - offsetX - LayoutWidth)
* @param offsetMax 最大的偏移量
*/
void onHorizontalDrag(float percentX, int offsetX, int offsetMax);
/**
* 是否支持水平方向的拖動(將會影響到onHorizontalDrag的調(diào)用)
* @return 水平拖動需要消耗更多的時間和資源朗若,所以如果不支持請返回false
*/
boolean isSupportHorizontalDrag();
代碼很簡單恼五,源碼中有中文注解就不一一說明
LottileAnimation
結(jié)合官網(wǎng)和部分源碼很好實現(xiàn)Lottie在RefreshHead 中的實現(xiàn) !?扌浮灾馒!
重點:
View,ViewGroup的生命周期和Wind上面的渲染過程,剛開始的時候去繼承View拿到當前.json動畫的寬高和在onMeasure中一直是0,0后來改為ViewGroup 子類重新自測measure寬和高得到的也是0,0。這下搞的我翻了波筆記本
筆記本mark入口
后來才決定改為CIRCLE_DIAMETER 和mCircleDiameter根據(jù)自己的分辨率和中心點來繪制動畫.json的大小
(主要為了適配動畫在不同分辨率手機里面的效果)
final DisplayMetrics metrics = getResources().getDisplayMetrics();
mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density);
小伙伴也可以使用AT_MOST來根據(jù)父布局的指定獲取當前自定義View的寬和高
OK到這里基本完善了Lottie動畫能在Header里面指定的位置跳動了,接下run了一次發(fā)現(xiàn)動畫確實是在Head里面跳動遣总,但是因為設(shè)置了looper(true)的屬性本身是不和Refresh onRefreshing 時間沖突,但是后面再次下拉刷新的時候出現(xiàn)了動畫的幀數(shù)不是原來第一幀睬罗,這下糾結(jié)了,我一般不喜歡手動導(dǎo)入三方源碼修改別人的源碼主要以前被(XXX)坑哭過旭斥,升級一次容达,我基本要上重構(gòu)一次我的項目。好在快速瀏覽了一遍LottieAnimationView的源碼,好在和我猜測的一樣 LottieDraw 和LottieAnimator 果然是根據(jù)Frame幀來實現(xiàn)動畫的過程
那么現(xiàn)在來了不是給我機會為所欲為最大幀和最小幀 整個圖片繪制過程Progress等
switch (newState) {
case None:
lav.setFrame(0);
lav.setProgress(0);
break;
case PullDownToRefresh:
lav.setVisibility(View.VISIBLE);
break;
case PullDownCanceled:
break;
case ReleaseToRefresh:
lav.setVisibility(View.VISIBLE);
break;
case Refreshing:
break;
case RefreshFinish:
lav.setVisibility(View.GONE);
break;
}
配合上層接口對代碼做了最后的處理
喜歡效果小伙伴可以在去關(guān)注SmartRefreshLayout refresh-heads 和fresh-foot代碼的實現(xiàn)垂券,其中Vector向量和對View花盐,ViewGroup,Drawable繪制 是很不錯的學(xué)習(xí)源碼羡滑。
OK 效果做出來了
經(jīng)驗分享:
記得幾年前做Android開發(fā)的時拿到第三方庫或者框架很是頭疼和煩躁,后面接觸多了能心平氣和的寫代碼算芯,反而覺得開發(fā)過程在別人車輪下面還是相對容易的柒昏,api 知識體系清楚的情況下,功能實現(xiàn)反而很輕松熙揍! 時代在進步职祷,人也在進步,學(xué)習(xí)是IT的必經(jīng)之路届囚,找準自己愛好堅持下去就行有梆。
以后博主每周五分享一篇博客(Kotilin React-native Android Flutter Java)