app/build.gradle
@@ -157,6 +157,7 @@ // implementation 'com.hdl.android:debugsdk:1.0.71' implementation project(path: ':sdk') implementation 'androidx.activity:activity:1.2.3' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' @@ -206,13 +207,13 @@ //分析和用户标签等功能(可选,集成极光分析SDK后,即可支持行为触发推送消息、推送转化率统计,用户行为分) implementation 'cn.jiguang.sdk:joperate:2.0.2' //若不集成厂商通道,可直接跳过以下依赖 // 极光厂商插件版本与接入 JPush 版本保持一致,下同 // 接入华为厂商 implementation 'com.huawei.hms:push:6.9.0.300' implementation 'cn.jiguang.sdk.plugin:huawei:5.0.3' implementation 'com.huawei.agconnect:agconnect-core:1.9.1.301' // // 接入 FCM 厂商 // implementation 'com.google.firebase:firebase-messaging:23.1.2' // implementation 'cn.jiguang.sdk.plugin:fcm:5.0.3' app/src/main/java/com/hdl/photovoltaic/ui/home/HomePageFragment.java
@@ -8,6 +8,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.location.LocationManager; import android.os.AsyncTask; import android.os.Bundle; import android.provider.Settings; import android.text.TextUtils; @@ -16,7 +17,6 @@ import androidx.annotation.NonNull; import androidx.appcompat.content.res.AppCompatResources; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.bigkoo.pickerview.builder.TimePickerBuilder; import com.bigkoo.pickerview.listener.OnTimeSelectListener; @@ -55,6 +55,8 @@ import com.hdl.photovoltaic.utils.TimeUtils; import com.hdl.photovoltaic.widget.ConfirmationCancelDialog; import com.hdl.photovoltaic.widget.FlashingBoxDialog; import com.hdl.photovoltaic.widget.refreshlayout.BGARefreshLayout; import com.hdl.photovoltaic.widget.refreshlayout.HDLRefreshViewHolder; import com.hdl.sdk.link.core.bean.eventbus.BaseEventBus; import org.greenrobot.eventbus.EventBus; @@ -70,7 +72,7 @@ * 首页模块 */ public class HomePageFragment extends CustomBaseFragment implements AAChartView.AAChartViewCallBack { public class HomePageFragment extends CustomBaseFragment implements AAChartView.AAChartViewCallBack, BGARefreshLayout.BGARefreshLayoutDelegate { FragmentHomePageBinding viewBinding; @@ -342,15 +344,14 @@ } }); //设置下拉箭头颜色 viewBinding.srl.setColorSchemeResources(R.color.text_FF245EC3); viewBinding.srl.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { viewBinding.srl.setRefreshing(false); readDate(); } }); // viewBinding.rsl.setColorSchemeResources(R.color.text_FF245EC3); // viewBinding.rsl.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { // @Override // public void onRefresh() { // viewBinding.rsl.setRefreshing(false); // readDate(); // } // }); } @@ -466,8 +467,18 @@ viewBinding.homePageStationSelectTimeLl.setVisibility(View.VISIBLE); viewBinding.autoSizeMyAAChart.setBackgroundColor(_mActivity.getColor(R.color.text_FF1C1C1E)); setUpAAChartView(true);//图表加载 processLogic(); } protected void processLogic() { viewBinding.rsl.setDelegate(this); HDLRefreshViewHolder hdlRefreshViewHolder = new HDLRefreshViewHolder(_mActivity, true); hdlRefreshViewHolder.setPullDownImageResource(R.mipmap.loading_44); hdlRefreshViewHolder.setChangeToReleaseRefreshAnimResId(R.drawable.loading_refresh); hdlRefreshViewHolder.setRefreshingAnimResId(R.drawable.loading_refresh_end); viewBinding.rsl.setRefreshViewHolder(hdlRefreshViewHolder); } @Override public void chartViewDidFinishLoad(AAChartView aaChartView) { @@ -501,7 +512,7 @@ * 电站状态概览 */ private void getStatusOverview() { HdlResidenceLogic.getInstance().getStatusOverview("","","",new CloudCallBeak<StatusOverviewBean>() { HdlResidenceLogic.getInstance().getStatusOverview("", "", "", new CloudCallBeak<StatusOverviewBean>() { @Override public void onSuccess(StatusOverviewBean statusOverviewBean) { if (statusOverviewBean == null) { @@ -726,4 +737,49 @@ } } @Override public void onBGARefreshLayoutBeginRefreshing(BGARefreshLayout refreshLayout) { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void aVoid) { viewBinding.rsl.endRefreshing(); //加载最新数据完成 readDate(); } }.execute(); } @Override public boolean onBGARefreshLayoutBeginLoadingMore(BGARefreshLayout refreshLayout) { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void aVoid) { viewBinding.rsl.endLoadingMore(); } }.execute(); return true; } } app/src/main/java/com/hdl/photovoltaic/ui/powerstation/HouseAndDeviceFragment.java
@@ -162,6 +162,9 @@ viewBinding.allLl.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (powerStationStatusValue.equals(PowerStationStatus.All)) { return; } powerStationStatusValue = PowerStationStatus.All; viewBinding.allLl.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.station_status_yes_ffffff)); viewBinding.faultsLl.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.station_status_no_ffffff)); @@ -180,6 +183,9 @@ viewBinding.faultsLl.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (powerStationStatusValue.equals(PowerStationStatus.malfunction)) { return; } powerStationStatusValue = PowerStationStatus.malfunction; viewBinding.allLl.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.station_status_no_ffffff)); viewBinding.faultsLl.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.station_status_yes_ffffff)); @@ -198,6 +204,9 @@ viewBinding.offlineLl.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (powerStationStatusValue.equals(PowerStationStatus.off)) { return; } powerStationStatusValue = PowerStationStatus.off; viewBinding.allLl.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.station_status_no_ffffff)); viewBinding.faultsLl.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.station_status_no_ffffff)); @@ -216,6 +225,9 @@ viewBinding.connectedLl.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (powerStationStatusValue.equals(PowerStationStatus.connecting)) { return; } powerStationStatusValue = PowerStationStatus.connecting; viewBinding.allLl.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.station_status_no_ffffff)); viewBinding.faultsLl.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.station_status_no_ffffff)); @@ -765,7 +777,7 @@ * 电站状态概览 */ private void getStatusOverview() { HdlResidenceLogic.getInstance().getStatusOverview(screeningConditionStatus,installedCapacityMinValue,installedCapacityMaxValue,new CloudCallBeak<StatusOverviewBean>() { HdlResidenceLogic.getInstance().getStatusOverview(screeningConditionStatus, installedCapacityMinValue, installedCapacityMaxValue, new CloudCallBeak<StatusOverviewBean>() { @Override public void onSuccess(StatusOverviewBean statusOverviewBean) { if (statusOverviewBean == null) { app/src/main/java/com/hdl/photovoltaic/widget/LoadingDialog.java
@@ -1,8 +1,6 @@ package com.hdl.photovoltaic.widget; import android.app.Dialog; import android.content.Context; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.text.TextUtils; import android.view.WindowManager; @@ -39,7 +37,7 @@ getWindow().setAttributes(params); Glide.with(context) // .asBitmap() // 强制作为静态图片加载 .load(R.drawable.loading) .load(R.drawable.loading_gif) .into(imageView); // content.setText(R.string.device_加载中); } app/src/main/java/com/hdl/photovoltaic/widget/refreshlayout/BGARefreshLayout.java
New file @@ -0,0 +1,923 @@ /** * Copyright 2015 bingoogolapple * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hdl.photovoltaic.widget.refreshlayout; import android.animation.ValueAnimator; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.webkit.WebView; import android.widget.AbsListView; import android.widget.LinearLayout; import android.widget.ScrollView; import androidx.recyclerview.widget.RecyclerView; import java.lang.reflect.Field; /** * 作者:王浩 邮件:bingoogolapple@gmail.com * 创建时间:15/5/21 22:35 * 描述:下拉刷新、上拉加载更多、可添加自定义(固定、可滑动)头部控件(例如慕课网app顶部的广告位) */ public class BGARefreshLayout extends LinearLayout { private static final String TAG = BGARefreshLayout.class.getSimpleName(); private BGARefreshViewHolder mRefreshViewHolder; /** * 整个头部控件,下拉刷新控件mRefreshHeaderView和下拉刷新控件下方的自定义组件mCustomHeaderView的父控件 */ private LinearLayout mWholeHeaderView; /** * 下拉刷新控件 */ private View mRefreshHeaderView; /** * 下拉刷新控件下方的自定义控件 */ private View mCustomHeaderView; /** * 下拉刷新控件下方的自定义控件是否可滚动,默认不可滚动 */ private boolean mIsCustomHeaderViewScrollable = false; /** * 下拉刷新控件的高度 */ private int mRefreshHeaderViewHeight; /** * 当前刷新状态 */ private RefreshStatus mCurrentRefreshStatus = RefreshStatus.IDLE; /** * 上拉加载更多控件 */ private View mLoadMoreFooterView; /** * 上拉加载更多控件的高度 */ private int mLoadMoreFooterViewHeight; /** * 下拉刷新和上拉加载更多代理 */ private BGARefreshLayoutDelegate mDelegate; /** * 手指按下时,y轴方向的偏移量 */ private int mWholeHeaderDownY = -1; /** * 整个头部控件最小的paddingTop */ private int mMinWholeHeaderViewPaddingTop; /** * 整个头部控件最大的paddingTop */ private int mMaxWholeHeaderViewPaddingTop; /** * 是否处于正在加载更多状态 */ private boolean mIsLoadingMore = false; private AbsListView mAbsListView; private ScrollView mScrollView; private RecyclerView mRecyclerView; private View mNormalView; private WebView mWebView; private BGAStickyNavLayout mStickyNavLayout; private NestedScrollLayout mNestedScrollLayout; private View mContentView; private float mInterceptTouchDownX = -1; private float mInterceptTouchDownY = -1; /** * 按下时整个头部控件的paddingTop */ private int mWholeHeaderViewDownPaddingTop = 0; /** * 记录开始下拉刷新时的downY */ private int mRefreshDownY = -1; /** * 是否已经设置内容控件滚动监听器 */ private boolean mIsInitedContentViewScrollListener = false; /** * 触发上拉加载更多时是否显示加载更多控件 */ private boolean mIsShowLoadingMoreView = true; /** * 下拉刷新是否可用 */ private boolean mPullDownRefreshEnable = true; private Handler mHandler; private BGARefreshScaleDelegate mRefreshScaleDelegate; private int mTouchSlop; public BGARefreshLayout(Context context) { this(context, null); } public BGARefreshLayout(Context context, AttributeSet attrs) { super(context, attrs); setOrientation(LinearLayout.VERTICAL); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mHandler = new Handler(Looper.getMainLooper()); initWholeHeaderView(); } /** * 初始化整个头部控件 */ private void initWholeHeaderView() { mWholeHeaderView = new LinearLayout(getContext()); mWholeHeaderView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); mWholeHeaderView.setOrientation(LinearLayout.VERTICAL); addView(mWholeHeaderView); } @Override public void onFinishInflate() { super.onFinishInflate(); if (getChildCount() != 2) { throw new RuntimeException(BGARefreshLayout.class.getSimpleName() + "必须有且只有一个子控件"); } mContentView = getChildAt(1); if (mContentView instanceof AbsListView) { mAbsListView = (AbsListView) mContentView; } else if (mContentView instanceof RecyclerView) { mRecyclerView = (RecyclerView) mContentView; } else if (mContentView instanceof ScrollView) { mScrollView = (ScrollView) mContentView; } else if (mContentView instanceof WebView) { mWebView = (WebView) mContentView; } else if (mContentView instanceof BGAStickyNavLayout) { mStickyNavLayout = (BGAStickyNavLayout) mContentView; mStickyNavLayout.setRefreshLayout(this); } else if (mContentView instanceof NestedScrollLayout) { mNestedScrollLayout = (NestedScrollLayout) mContentView; } else { mNormalView = mContentView; // 设置为可点击,否则在空白区域无法拖动 mNormalView.setClickable(true); } } public void setRefreshViewHolder(BGARefreshViewHolder refreshViewHolder) { mRefreshViewHolder = refreshViewHolder; mRefreshViewHolder.setRefreshLayout(this); initRefreshHeaderView(); initLoadMoreFooterView(); } public void startChangeWholeHeaderViewPaddingTop(int distance) { ValueAnimator animator = ValueAnimator.ofInt(mWholeHeaderView.getPaddingTop(), mWholeHeaderView.getPaddingTop() - distance); animator.setDuration(mRefreshViewHolder.getTopAnimDuration()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int paddingTop = (int) animation.getAnimatedValue(); mWholeHeaderView.setPadding(0, paddingTop, 0, 0); } }); animator.start(); } /** * 初始化下拉刷新控件 * * @return */ private void initRefreshHeaderView() { mRefreshHeaderView = mRefreshViewHolder.getRefreshHeaderView(); if (mRefreshHeaderView != null) { mRefreshHeaderView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); mRefreshHeaderViewHeight = mRefreshViewHolder.getRefreshHeaderViewHeight(); mMinWholeHeaderViewPaddingTop = -mRefreshHeaderViewHeight; mMaxWholeHeaderViewPaddingTop = (int) (mRefreshHeaderViewHeight * mRefreshViewHolder.getSpringDistanceScale()); mWholeHeaderView.setPadding(0, mMinWholeHeaderViewPaddingTop, 0, 0); mWholeHeaderView.addView(mRefreshHeaderView, 0); } } /** * 设置下拉刷新控件下方的自定义控件 * * @param customHeaderView 下拉刷新控件下方的自定义控件 * @param scrollable 是否可以滚动 */ public void setCustomHeaderView(View customHeaderView, boolean scrollable) { if (mCustomHeaderView != null && mCustomHeaderView.getParent() != null) { ViewGroup parent = (ViewGroup) mCustomHeaderView.getParent(); parent.removeView(mCustomHeaderView); } mCustomHeaderView = customHeaderView; if (mCustomHeaderView != null) { mCustomHeaderView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); mWholeHeaderView.addView(mCustomHeaderView); mIsCustomHeaderViewScrollable = scrollable; } } /** * 初始化上拉加载更多控件 * * @return */ private void initLoadMoreFooterView() { mLoadMoreFooterView = mRefreshViewHolder.getLoadMoreFooterView(); if (mLoadMoreFooterView != null) { // 测量上拉加载更多控件的高度 mLoadMoreFooterView.measure(0, 0); mLoadMoreFooterViewHeight = mLoadMoreFooterView.getMeasuredHeight(); mLoadMoreFooterView.setVisibility(GONE); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); // 被添加到窗口后再设置监听器,这样开发者就不必烦恼先初始化RefreshLayout还是先设置自定义滚动监听器 if (!mIsInitedContentViewScrollListener && mLoadMoreFooterView != null) { setRecyclerViewOnScrollListener(); setAbsListViewOnScrollListener(); addView(mLoadMoreFooterView, getChildCount()); mIsInitedContentViewScrollListener = true; } } private void setRecyclerViewOnScrollListener() { if (mRecyclerView != null) { mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if ((newState == RecyclerView.SCROLL_STATE_IDLE || newState == RecyclerView.SCROLL_STATE_SETTLING) && shouldHandleRecyclerViewLoadingMore(mRecyclerView)) { beginLoadingMore(); } } }); } } private void setAbsListViewOnScrollListener() { if (mAbsListView != null) { try { // 通过反射获取开发者自定义的滚动监听器,并将其替换成自己的滚动监听器,触发滚动时也要通知开发者自定义的滚动监听器(非侵入式,不让开发者继承特定的控件) // mAbsListView.getClass().getDeclaredField("mOnScrollListener")获取不到mOnScrollListener,必须通过AbsListView.class.getDeclaredField("mOnScrollListener")获取 Field field = AbsListView.class.getDeclaredField("mOnScrollListener"); field.setAccessible(true); // 开发者自定义的滚动监听器 final AbsListView.OnScrollListener onScrollListener = (AbsListView.OnScrollListener) field.get(mAbsListView); mAbsListView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView absListView, int scrollState) { if ((scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_FLING) && shouldHandleAbsListViewLoadingMore(mAbsListView)) { beginLoadingMore(); } if (onScrollListener != null) { onScrollListener.onScrollStateChanged(absListView, scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (onScrollListener != null) { onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } } }); } catch (Exception e) { e.printStackTrace(); } } } public boolean shouldHandleAbsListViewLoadingMore(AbsListView absListView) { if (mIsLoadingMore || mCurrentRefreshStatus == RefreshStatus.REFRESHING || mLoadMoreFooterView == null || mDelegate == null || absListView == null || absListView.getAdapter() == null || absListView.getAdapter().getCount() == 0) { return false; } return BGARefreshScrollingUtil.isAbsListViewToBottom(absListView); } public boolean shouldHandleRecyclerViewLoadingMore(RecyclerView recyclerView) { if (mIsLoadingMore || mCurrentRefreshStatus == RefreshStatus.REFRESHING || mLoadMoreFooterView == null || mDelegate == null || recyclerView.getAdapter() == null || recyclerView.getAdapter().getItemCount() == 0) { return false; } return BGARefreshScrollingUtil.isRecyclerViewToBottom(recyclerView); } /** * 是否满足处理刷新的条件 * * @return */ private boolean shouldHandleLoadingMore() { if (mIsLoadingMore || mCurrentRefreshStatus == RefreshStatus.REFRESHING || mLoadMoreFooterView == null || mDelegate == null) { return false; } // 内容是普通控件,满足 if (mNormalView != null) { return true; } if (BGARefreshScrollingUtil.isWebViewToBottom(mWebView)) { return true; } if (BGARefreshScrollingUtil.isScrollViewToBottom(mScrollView)) { return true; } if (mAbsListView != null) { return shouldHandleAbsListViewLoadingMore(mAbsListView); } if (mRecyclerView != null) { return shouldHandleRecyclerViewLoadingMore(mRecyclerView); } if (mStickyNavLayout != null) { return mStickyNavLayout.shouldHandleLoadingMore(); } if (BGARefreshScrollingUtil.isNestedScrollLayoutToBottom(mNestedScrollLayout)) { return true; } return false; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mInterceptTouchDownX = event.getRawX(); mInterceptTouchDownY = event.getRawY(); break; case MotionEvent.ACTION_MOVE: if (!mIsLoadingMore && (mCurrentRefreshStatus != RefreshStatus.REFRESHING)) { if (mInterceptTouchDownX == -1) { mInterceptTouchDownX = (int) event.getRawX(); } if (mInterceptTouchDownY == -1) { mInterceptTouchDownY = (int) event.getRawY(); } int interceptTouchMoveDistanceY = (int) (event.getRawY() - mInterceptTouchDownY); // 可以没有上拉加载更多,但是必须有下拉刷新,否则就不拦截事件 if (Math.abs(event.getRawX() - mInterceptTouchDownX) < Math.abs(interceptTouchMoveDistanceY) && mRefreshHeaderView != null) { if ((interceptTouchMoveDistanceY > mTouchSlop && shouldHandleRefresh()) || (interceptTouchMoveDistanceY < -mTouchSlop && shouldHandleLoadingMore()) || (interceptTouchMoveDistanceY < -mTouchSlop && !isWholeHeaderViewCompleteInvisible()) || (interceptTouchMoveDistanceY > mTouchSlop && shouldInterceptToMoveCustomHeaderViewDown())) { // ACTION_DOWN时没有消耗掉事件,子控件会处于按下状态,这里设置ACTION_CANCEL,使子控件取消按下状态 event.setAction(MotionEvent.ACTION_CANCEL); super.onInterceptTouchEvent(event); return true; } } } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // 重置 mInterceptTouchDownX = -1; mInterceptTouchDownY = -1; break; } return super.onInterceptTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mIsCustomHeaderViewScrollable && !isWholeHeaderViewCompleteInvisible()) { super.dispatchTouchEvent(ev); return true; } return super.dispatchTouchEvent(ev); } /** * 是否满足处理刷新的条件 * * @return */ private boolean shouldHandleRefresh() { if (!mPullDownRefreshEnable || mIsLoadingMore || mCurrentRefreshStatus == RefreshStatus.REFRESHING || mRefreshHeaderView == null || mDelegate == null) { return false; } return isContentViewToTop(); } private boolean isContentViewToTop() { // 内容是普通控件,满足 if (mNormalView != null) { return true; } if (BGARefreshScrollingUtil.isScrollViewOrWebViewToTop(mWebView)) { return true; } if (BGARefreshScrollingUtil.isScrollViewOrWebViewToTop(mScrollView)) { return true; } if (BGARefreshScrollingUtil.isAbsListViewToTop(mAbsListView)) { return true; } if (BGARefreshScrollingUtil.isRecyclerViewToTop(mRecyclerView)) { return true; } if (BGARefreshScrollingUtil.isStickyNavLayoutToTop(mStickyNavLayout)) { return true; } if (BGARefreshScrollingUtil.isScrollViewOrWebViewToTop(mNestedScrollLayout)) { return true; } return false; } private boolean shouldInterceptToMoveCustomHeaderViewDown() { return isContentViewToTop() && mCustomHeaderView != null && mIsCustomHeaderViewScrollable && !isCustomHeaderViewCompleteVisible(); } private boolean shouldInterceptToMoveCustomHeaderViewUp() { return isContentViewToTop() && mCustomHeaderView != null && mIsCustomHeaderViewScrollable && !isWholeHeaderViewCompleteInvisible(); } @Override public boolean onTouchEvent(MotionEvent event) { if (null != mRefreshHeaderView) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mWholeHeaderDownY = (int) event.getY(); if (mCustomHeaderView != null) { mWholeHeaderViewDownPaddingTop = mWholeHeaderView.getPaddingTop(); } if (mCustomHeaderView == null || !mIsCustomHeaderViewScrollable) { mRefreshDownY = (int) event.getY(); } if (isWholeHeaderViewCompleteInvisible()) { mRefreshDownY = (int) event.getY(); return true; } break; case MotionEvent.ACTION_MOVE: if (handleActionMove(event)) { return true; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (handleActionUpOrCancel(event)) { return true; } break; default: break; } } return super.onTouchEvent(event); } /** * 自定义头部控件是否已经完全显示 * * @return true表示已经完全显示,false表示没有完全显示 */ private boolean isCustomHeaderViewCompleteVisible() { if (mCustomHeaderView != null) { // 0表示x,1表示y int[] location = new int[2]; getLocationOnScreen(location); int mOnScreenY = location[1]; mCustomHeaderView.getLocationOnScreen(location); int customHeaderViewOnScreenY = location[1]; if (mOnScreenY <= customHeaderViewOnScreenY) { return true; } else { return false; } } return true; } /** * 整个头部控件是否已经完全隐藏 * * @return true表示完全隐藏,false表示没有完全隐藏 */ private boolean isWholeHeaderViewCompleteInvisible() { if (mCustomHeaderView != null && mIsCustomHeaderViewScrollable) { // 0表示x,1表示y int[] location = new int[2]; getLocationOnScreen(location); int mOnScreenY = location[1]; mWholeHeaderView.getLocationOnScreen(location); int wholeHeaderViewOnScreenY = location[1]; if (wholeHeaderViewOnScreenY + mWholeHeaderView.getMeasuredHeight() <= mOnScreenY) { return true; } else { return false; } } return true; } /** * 处理手指滑动事件 * * @param event * @return true表示自己消耗掉该事件,false表示不消耗该事件 */ private boolean handleActionMove(MotionEvent event) { if (mCurrentRefreshStatus == RefreshStatus.REFRESHING || mIsLoadingMore) { return false; } if ((mCustomHeaderView == null || !mIsCustomHeaderViewScrollable) && mRefreshDownY == -1) { mRefreshDownY = (int) event.getY(); } if (mCustomHeaderView != null && mIsCustomHeaderViewScrollable && isCustomHeaderViewCompleteVisible() && mRefreshDownY == -1) { mRefreshDownY = (int) event.getY(); } int refreshDiffY = (int) event.getY() - mRefreshDownY; refreshDiffY = (int) (refreshDiffY / mRefreshViewHolder.getPaddingTopScale()); // 如果是向下拉,并且当前可见的第一个条目的索引等于0,才处理整个头部控件的padding if (refreshDiffY > 0 && shouldHandleRefresh() && isCustomHeaderViewCompleteVisible()) { int paddingTop = mMinWholeHeaderViewPaddingTop + refreshDiffY; if (paddingTop > 0 && mCurrentRefreshStatus != RefreshStatus.RELEASE_REFRESH) { // 下拉刷新控件完全显示,并且当前状态没有处于释放开始刷新状态 mCurrentRefreshStatus = RefreshStatus.RELEASE_REFRESH; handleRefreshStatusChanged(); mRefreshViewHolder.handleScale(1.0f, refreshDiffY); if (mRefreshScaleDelegate != null) { mRefreshScaleDelegate.onRefreshScaleChanged(1.0f, refreshDiffY); } } else if (paddingTop < 0) { // 下拉刷新控件没有完全显示,并且当前状态没有处于下拉刷新状态 if (mCurrentRefreshStatus != RefreshStatus.PULL_DOWN) { boolean isPreRefreshStatusNotIdle = mCurrentRefreshStatus != RefreshStatus.IDLE; mCurrentRefreshStatus = RefreshStatus.PULL_DOWN; if (isPreRefreshStatusNotIdle) { handleRefreshStatusChanged(); } } float scale = 1 - paddingTop * 1.0f / mMinWholeHeaderViewPaddingTop; /** * 往下滑 * paddingTop mMinWholeHeaderViewPaddingTop 到 0 * scale 0 到 1 * 往上滑 * paddingTop 0 到 mMinWholeHeaderViewPaddingTop * scale 1 到 0 */ mRefreshViewHolder.handleScale(scale, refreshDiffY); if (mRefreshScaleDelegate != null) { mRefreshScaleDelegate.onRefreshScaleChanged(scale, refreshDiffY); } } paddingTop = Math.min(paddingTop, mMaxWholeHeaderViewPaddingTop); mWholeHeaderView.setPadding(0, paddingTop, 0, 0); if (mRefreshViewHolder.canChangeToRefreshingStatus()) { mWholeHeaderDownY = -1; mRefreshDownY = -1; beginRefreshing(); } return true; } if (mCustomHeaderView != null && mIsCustomHeaderViewScrollable) { if (mWholeHeaderDownY == -1) { mWholeHeaderDownY = (int) event.getY(); if (mCustomHeaderView != null) { mWholeHeaderViewDownPaddingTop = mWholeHeaderView.getPaddingTop(); } } int wholeHeaderDiffY = (int) event.getY() - mWholeHeaderDownY; if ((mPullDownRefreshEnable && !isWholeHeaderViewCompleteInvisible()) || (wholeHeaderDiffY > 0 && shouldInterceptToMoveCustomHeaderViewDown()) || (wholeHeaderDiffY < 0 && shouldInterceptToMoveCustomHeaderViewUp())) { int paddingTop = mWholeHeaderViewDownPaddingTop + wholeHeaderDiffY; if (paddingTop < mMinWholeHeaderViewPaddingTop - mCustomHeaderView.getMeasuredHeight()) { paddingTop = mMinWholeHeaderViewPaddingTop - mCustomHeaderView.getMeasuredHeight(); } mWholeHeaderView.setPadding(0, paddingTop, 0, 0); return true; } } return false; } /** * 处理手指抬起事件 * * @return true表示自己消耗掉该事件,false表示不消耗该事件 */ private boolean handleActionUpOrCancel(MotionEvent event) { boolean isReturnTrue = false; // 如果当前头部刷新控件没有完全隐藏,则需要返回true,自己消耗ACTION_UP事件 if ((mCustomHeaderView == null || (mCustomHeaderView != null && !mIsCustomHeaderViewScrollable)) && mWholeHeaderView.getPaddingTop() != mMinWholeHeaderViewPaddingTop) { isReturnTrue = true; } if (mCurrentRefreshStatus == RefreshStatus.PULL_DOWN || mCurrentRefreshStatus == RefreshStatus.IDLE) { // 处于下拉刷新状态,松手时隐藏下拉刷新控件 if (mCustomHeaderView == null || (mCustomHeaderView != null && mWholeHeaderView.getPaddingTop() < 0 && mWholeHeaderView.getPaddingTop() > mMinWholeHeaderViewPaddingTop)) { hiddenRefreshHeaderView(); } mCurrentRefreshStatus = RefreshStatus.IDLE; handleRefreshStatusChanged(); } else if (mCurrentRefreshStatus == RefreshStatus.RELEASE_REFRESH) { // 处于松开进入刷新状态,松手时完全显示下拉刷新控件,进入正在刷新状态 beginRefreshing(); } if (mRefreshDownY == -1) { mRefreshDownY = (int) event.getY(); } int diffY = (int) event.getY() - mRefreshDownY; if (shouldHandleLoadingMore() && diffY <= 0) { // 处理上拉加载更多,需要返回true,自己消耗ACTION_UP事件 isReturnTrue = true; beginLoadingMore(); } mWholeHeaderDownY = -1; mRefreshDownY = -1; return isReturnTrue; } /** * 处理下拉刷新控件状态变化 */ private void handleRefreshStatusChanged() { switch (mCurrentRefreshStatus) { case IDLE: mRefreshViewHolder.changeToIdle(); break; case PULL_DOWN: mRefreshViewHolder.changeToPullDown(); break; case RELEASE_REFRESH: mRefreshViewHolder.changeToReleaseRefresh(); break; case REFRESHING: mRefreshViewHolder.changeToRefreshing(); break; default: break; } } /** * 切换到正在刷新状态,会调用delegate的onBGARefreshLayoutBeginRefreshing方法 */ public void beginRefreshing() { if (mCurrentRefreshStatus != RefreshStatus.REFRESHING && mDelegate != null) { mCurrentRefreshStatus = RefreshStatus.REFRESHING; changeRefreshHeaderViewToZero(); handleRefreshStatusChanged(); mDelegate.onBGARefreshLayoutBeginRefreshing(this); } } /** * 结束下拉刷新 */ public void endRefreshing() { if (mCurrentRefreshStatus == RefreshStatus.REFRESHING) { mCurrentRefreshStatus = RefreshStatus.IDLE; hiddenRefreshHeaderView(); handleRefreshStatusChanged(); mRefreshViewHolder.onEndRefreshing(); } } /** * 隐藏下拉刷新控件,带动画 */ private void hiddenRefreshHeaderView() { ValueAnimator animator = ValueAnimator.ofInt(mWholeHeaderView.getPaddingTop(), mMinWholeHeaderViewPaddingTop); animator.setDuration(mRefreshViewHolder.getTopAnimDuration()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int paddingTop = (int) animation.getAnimatedValue(); mWholeHeaderView.setPadding(0, paddingTop, 0, 0); } }); animator.start(); } /** * 设置下拉刷新控件的paddingTop到0,带动画 */ private void changeRefreshHeaderViewToZero() { ValueAnimator animator = ValueAnimator.ofInt(mWholeHeaderView.getPaddingTop(), 0); animator.setDuration(mRefreshViewHolder.getTopAnimDuration()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int paddingTop = (int) animation.getAnimatedValue(); mWholeHeaderView.setPadding(0, paddingTop, 0, 0); } }); animator.start(); } /** * 开始上拉加载更多,会触发delegate的onBGARefreshLayoutBeginRefreshing方法 */ public void beginLoadingMore() { if (!mIsLoadingMore && mLoadMoreFooterView != null && mDelegate != null && mDelegate.onBGARefreshLayoutBeginLoadingMore(this)) { mIsLoadingMore = true; if (mIsShowLoadingMoreView) { showLoadingMoreView(); } } } /** * 显示上拉加载更多控件 */ private void showLoadingMoreView() { mRefreshViewHolder.changeToLoadingMore(); mLoadMoreFooterView.setVisibility(VISIBLE); BGARefreshScrollingUtil.scrollToBottom(mScrollView); BGARefreshScrollingUtil.scrollToBottom(mRecyclerView); BGARefreshScrollingUtil.scrollToBottom(mAbsListView); if (mStickyNavLayout != null) { mStickyNavLayout.scrollToBottom(); } } /** * 结束上拉加载更多 */ public void endLoadingMore() { if (mIsLoadingMore) { if (mIsShowLoadingMoreView) { // 避免WiFi环境下请求数据太快,加载更多控件一闪而过 mHandler.postDelayed(mDelayHiddenLoadingMoreViewTask, 300); } else { mIsLoadingMore = false; } } } private Runnable mDelayHiddenLoadingMoreViewTask = new Runnable() { @Override public void run() { mIsLoadingMore = false; mRefreshViewHolder.onEndLoadingMore(); mLoadMoreFooterView.setVisibility(GONE); } }; /** * 上拉加载更多时是否显示加载更多控件 * * @param isShowLoadingMoreView */ public void setIsShowLoadingMoreView(boolean isShowLoadingMoreView) { mIsShowLoadingMoreView = isShowLoadingMoreView; } /** * 设置下拉刷新是否可用 * * @param pullDownRefreshEnable */ public void setPullDownRefreshEnable(boolean pullDownRefreshEnable) { mPullDownRefreshEnable = pullDownRefreshEnable; } /** * 设置下拉刷新上拉加载更多代理 * * @param delegate */ public void setDelegate(BGARefreshLayoutDelegate delegate) { mDelegate = delegate; } /** * 设置下拉刷新控件可见时,上下拉进度的代理 * * @param refreshScaleDelegate */ public void setRefreshScaleDelegate(BGARefreshScaleDelegate refreshScaleDelegate) { mRefreshScaleDelegate = refreshScaleDelegate; } /** * 获取当前下拉刷新的状态 * * @return */ public RefreshStatus getCurrentRefreshStatus() { return mCurrentRefreshStatus; } /** * 是否处于正在加载更多状态 * * @return */ public boolean isLoadingMore() { return mIsLoadingMore; } public interface BGARefreshLayoutDelegate { /** * 开始刷新 */ void onBGARefreshLayoutBeginRefreshing(BGARefreshLayout refreshLayout); /** * 开始加载更多。由于监听了ScrollView、RecyclerView、AbsListView滚动到底部的事件,所以这里采用返回boolean来处理是否加载更多。否则使用endLoadingMore方法会失效 * * @param refreshLayout * @return 如果要开始加载更多则返回true,否则返回false。(返回false的场景:没有网络、一共只有x页数据并且已经加载了x页数据了) */ boolean onBGARefreshLayoutBeginLoadingMore(BGARefreshLayout refreshLayout); } public interface BGARefreshScaleDelegate { /** * 下拉刷新控件可见时,处理上下拉进度 * * @param scale 下拉过程0 到 1,回弹过程1 到 0,没有加上弹簧距离移动时的比例 * @param moveYDistance 整个下拉刷新控件paddingTop变化的值,如果有弹簧距离,会大于整个下拉刷新控件的高度 */ void onRefreshScaleChanged(float scale, int moveYDistance); } public enum RefreshStatus { IDLE, PULL_DOWN, RELEASE_REFRESH, REFRESHING } public static int dp2px(Context context, int dpValue) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, context.getResources().getDisplayMetrics()); } } app/src/main/java/com/hdl/photovoltaic/widget/refreshlayout/BGARefreshScrollingUtil.java
New file @@ -0,0 +1,292 @@ /** * Copyright 2015 bingoogolapple * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hdl.photovoltaic.widget.refreshlayout; import android.content.Context; import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.View; import android.view.ViewParent; import android.view.WindowManager; import android.webkit.WebView; import android.widget.AbsListView; import android.widget.ScrollView; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.StaggeredGridLayoutManager; import java.lang.reflect.Field; /** * 作者:王浩 邮件:bingoogolapple@gmail.com * 创建时间:15/10/28 上午2:26 * 描述: */ public class BGARefreshScrollingUtil { private BGARefreshScrollingUtil() { } public static boolean isScrollViewOrWebViewToTop(View view) { return view != null && view.getScrollY() == 0; } public static boolean isAbsListViewToTop(AbsListView absListView) { if (absListView != null) { int firstChildTop = 0; if (absListView.getChildCount() > 0) { // 如果AdapterView的子控件数量不为0,获取第一个子控件的top firstChildTop = absListView.getChildAt(0).getTop() - absListView.getPaddingTop(); } if (absListView.getFirstVisiblePosition() == 0 && firstChildTop == 0) { return true; } } return false; } public static boolean isRecyclerViewToTop(RecyclerView recyclerView) { if (recyclerView != null) { RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager == null) { return true; } if (manager.getItemCount() == 0) { return true; } if (manager instanceof LinearLayoutManager) { LinearLayoutManager layoutManager = (LinearLayoutManager) manager; int firstChildTop = 0; if (recyclerView.getChildCount() > 0) { // 处理item高度超过一屏幕时的情况 View firstVisibleChild = recyclerView.getChildAt(0); if (firstVisibleChild != null && firstVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) { if (android.os.Build.VERSION.SDK_INT < 14) { return !(ViewCompat.canScrollVertically(recyclerView, -1) || recyclerView.getScrollY() > 0); } else { return !ViewCompat.canScrollVertically(recyclerView, -1); } } // 如果RecyclerView的子控件数量不为0,获取第一个子控件的top // 解决item的topMargin不为0时不能触发下拉刷新 View firstChild = recyclerView.getChildAt(0); RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) firstChild.getLayoutParams(); firstChildTop = firstChild.getTop() - layoutParams.topMargin - getRecyclerViewItemTopInset(layoutParams) - recyclerView.getPaddingTop(); } if (layoutManager.findFirstCompletelyVisibleItemPosition() < 1 && firstChildTop == 0) { return true; } } else if (manager instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager; int[] out = layoutManager.findFirstCompletelyVisibleItemPositions(null); if (out[0] < 1) { return true; } } } return false; } /** * 通过反射获取RecyclerView的item的topInset * * @param layoutParams * @return */ private static int getRecyclerViewItemTopInset(RecyclerView.LayoutParams layoutParams) { try { Field field = RecyclerView.LayoutParams.class.getDeclaredField("mDecorInsets"); field.setAccessible(true); // 开发者自定义的滚动监听器 Rect decorInsets = (Rect) field.get(layoutParams); return decorInsets.top; } catch (Exception e) { e.printStackTrace(); } return 0; } public static boolean isStickyNavLayoutToTop(BGAStickyNavLayout stickyNavLayout) { return isScrollViewOrWebViewToTop(stickyNavLayout) && stickyNavLayout.isContentViewToTop(); } public static boolean isWebViewToBottom(WebView webView) { return webView != null && webView.getContentHeight() * webView.getScale() == (webView.getScrollY() + webView.getMeasuredHeight()); } public static boolean isScrollViewToBottom(ScrollView scrollView) { if (scrollView != null) { int scrollContentHeight = scrollView.getScrollY() + scrollView.getMeasuredHeight() - scrollView.getPaddingTop() - scrollView.getPaddingBottom(); int realContentHeight = scrollView.getChildAt(0).getMeasuredHeight(); if (scrollContentHeight == realContentHeight) { return true; } } return false; } public static boolean isNestedScrollLayoutToBottom(NestedScrollLayout nestedScrollLayout) { if (nestedScrollLayout != null) { int scrollContentHeight = nestedScrollLayout.getScrollY() + nestedScrollLayout.getMeasuredHeight() - nestedScrollLayout.getPaddingTop() - nestedScrollLayout.getPaddingBottom(); int realContentHeight = nestedScrollLayout.getChildAt(0).getMeasuredHeight(); if (scrollContentHeight == realContentHeight) { return true; } } return false; } public static boolean isAbsListViewToBottom(AbsListView absListView) { if (absListView != null && absListView.getAdapter() != null && absListView.getChildCount() > 0 && absListView.getLastVisiblePosition() == absListView.getAdapter().getCount() - 1) { View lastChild = absListView.getChildAt(absListView.getChildCount() - 1); BGAStickyNavLayout stickyNavLayout = getStickyNavLayout(absListView); if (stickyNavLayout != null) { // 处理BGAStickyNavLayout中lastChild.getBottom() <= absListView.getMeasuredHeight()失效问题 // 0表示x,1表示y int[] location = new int[2]; lastChild.getLocationOnScreen(location); int lastChildBottomOnScreen = location[1] + lastChild.getMeasuredHeight(); stickyNavLayout.getLocationOnScreen(location); int stickyNavLayoutBottomOnScreen = location[1] + stickyNavLayout.getMeasuredHeight(); return lastChildBottomOnScreen + absListView.getPaddingBottom() <= stickyNavLayoutBottomOnScreen; } else { return lastChild.getBottom() <= absListView.getMeasuredHeight(); } } return false; } public static boolean isRecyclerViewToBottom(RecyclerView recyclerView) { if (recyclerView != null) { RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager == null || manager.getItemCount() == 0) { return false; } if (manager instanceof LinearLayoutManager) { // 处理item高度超过一屏幕时的情况 View lastVisibleChild = recyclerView.getChildAt(recyclerView.getChildCount() - 1); if (lastVisibleChild != null && lastVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) { if (android.os.Build.VERSION.SDK_INT < 14) { return !(ViewCompat.canScrollVertically(recyclerView, 1) || recyclerView.getScrollY() < 0); } else { return !ViewCompat.canScrollVertically(recyclerView, 1); } } LinearLayoutManager layoutManager = (LinearLayoutManager) manager; if (layoutManager.findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1) { BGAStickyNavLayout stickyNavLayout = getStickyNavLayout(recyclerView); if (stickyNavLayout != null) { // 处理BGAStickyNavLayout中findLastCompletelyVisibleItemPosition失效问题 View lastCompletelyVisibleChild = layoutManager.getChildAt(layoutManager.findLastCompletelyVisibleItemPosition()); if (lastCompletelyVisibleChild == null) { return true; } else { // 0表示x,1表示y int[] location = new int[2]; lastCompletelyVisibleChild.getLocationOnScreen(location); int lastChildBottomOnScreen = location[1] + lastCompletelyVisibleChild.getMeasuredHeight(); stickyNavLayout.getLocationOnScreen(location); int stickyNavLayoutBottomOnScreen = location[1] + stickyNavLayout.getMeasuredHeight(); return lastChildBottomOnScreen <= stickyNavLayoutBottomOnScreen; } } else { return true; } } } else if (manager instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager; int[] out = layoutManager.findLastCompletelyVisibleItemPositions(null); int lastPosition = layoutManager.getItemCount() - 1; for (int position : out) { if (position == lastPosition) { return true; } } } } return false; } public static void scrollToBottom(final ScrollView scrollView) { if (scrollView != null) { scrollView.post(new Runnable() { @Override public void run() { scrollView.fullScroll(ScrollView.FOCUS_DOWN); } }); } } public static void scrollToBottom(final AbsListView absListView) { if (absListView != null) { if (absListView.getAdapter() != null && absListView.getAdapter().getCount() > 0) { absListView.post(new Runnable() { @Override public void run() { absListView.setSelection(absListView.getAdapter().getCount() - 1); } }); } } } public static void scrollToBottom(final RecyclerView recyclerView) { if (recyclerView != null) { if (recyclerView.getAdapter() != null && recyclerView.getAdapter().getItemCount() > 0) { recyclerView.post(new Runnable() { @Override public void run() { recyclerView.smoothScrollToPosition(recyclerView.getAdapter().getItemCount() - 1); } }); } } } public static BGAStickyNavLayout getStickyNavLayout(View view) { ViewParent viewParent = view.getParent(); while (viewParent != null) { if (viewParent instanceof BGAStickyNavLayout) { return (BGAStickyNavLayout) viewParent; } viewParent = viewParent.getParent(); } return null; } public static int getScreenHeight(Context context) { WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics(); windowManager.getDefaultDisplay().getMetrics(dm); return dm.heightPixels; } } app/src/main/java/com/hdl/photovoltaic/widget/refreshlayout/BGARefreshViewHolder.java
New file @@ -0,0 +1,345 @@ /** * Copyright 2015 bingoogolapple * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hdl.photovoltaic.widget.refreshlayout; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.AnimationDrawable; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.ColorRes; import androidx.annotation.DrawableRes; import com.hdl.photovoltaic.R; /** * 作者:王浩 邮件:bingoogolapple@gmail.com * 创建时间:15/5/21 12:56 * 描述:继承该抽象类实现响应的抽象方法,做出各种下拉刷新效果。参考BGANormalRefreshViewHolder、BGAStickinessRefreshViewHolder、BGAMoocStyleRefreshViewHolder、BGAMeiTuanRefreshViewHolder */ public abstract class BGARefreshViewHolder { /** * 手指移动距离与下拉刷新控件paddingTop移动距离的比值 */ private static final float PULL_DISTANCE_SCALE = 1.8f; /** * 手指移动距离与下拉刷新控件paddingTop移动距离的比值,默认1.8f */ private float mPullDistanceScale = PULL_DISTANCE_SCALE; /** * 下拉刷新控件paddingTop的弹簧距离与下拉刷新控件高度的比值 */ private static final float SPRING_DISTANCE_SCALE = 0.4f; /** * 下拉刷新控件paddingTop的弹簧距离与下拉刷新控件高度的比值,默认0.4f */ private float mSpringDistanceScale = SPRING_DISTANCE_SCALE; protected Context mContext; /** * 下拉刷新上拉加载更多控件 */ protected BGARefreshLayout mRefreshLayout; /** * 下拉刷新控件 */ protected View mRefreshHeaderView; /** * 上拉加载更多控件 */ protected View mLoadMoreFooterView; /** * 底部加载更多提示控件 */ protected TextView mFooterStatusTv; /** * 底部加载更多菊花控件 */ protected ImageView mFooterChrysanthemumIv; /** * 底部加载更多菊花drawable */ protected AnimationDrawable mFooterChrysanthemumAd; /** * 正在加载更多时的文本 */ protected String mLodingMoreText = "加载中..."; /** * 是否开启加载更多功能 */ private boolean mIsLoadingMoreEnabled = true; /** * 整个加载更多控件的背景颜色资源id */ private int mLoadMoreBackgroundColorRes = -1; /** * 整个加载更多控件的背景drawable资源id */ private int mLoadMoreBackgroundDrawableRes = -1; /** * 下拉刷新控件的背景颜色资源id */ protected int mRefreshViewBackgroundColorRes = -1; /** * 下拉刷新控件的背景drawable资源id */ protected int mRefreshViewBackgroundDrawableRes = -1; /** * 头部控件移动动画时常 */ private int mTopAnimDuration = 500; /** * @param context * @param isLoadingMoreEnabled 上拉加载更多是否可用 */ public BGARefreshViewHolder(Context context, boolean isLoadingMoreEnabled) { mContext = context; mIsLoadingMoreEnabled = isLoadingMoreEnabled; } /** * 设置正在加载更多时的文本 * * @param loadingMoreText */ public void setLoadingMoreText(String loadingMoreText) { mLodingMoreText = loadingMoreText; } /** * 设置整个加载更多控件的背景颜色资源id * * @param loadMoreBackgroundColorRes */ public void setLoadMoreBackgroundColorRes(@ColorRes int loadMoreBackgroundColorRes) { mLoadMoreBackgroundColorRes = loadMoreBackgroundColorRes; } /** * 设置整个加载更多控件的背景drawable资源id * * @param loadMoreBackgroundDrawableRes */ public void setLoadMoreBackgroundDrawableRes(@DrawableRes int loadMoreBackgroundDrawableRes) { mLoadMoreBackgroundDrawableRes = loadMoreBackgroundDrawableRes; } /** * 设置下拉刷新控件的背景颜色资源id * * @param refreshViewBackgroundColorRes */ public void setRefreshViewBackgroundColorRes(@ColorRes int refreshViewBackgroundColorRes) { mRefreshViewBackgroundColorRes = refreshViewBackgroundColorRes; } /** * 设置下拉刷新控件的背景drawable资源id * * @param refreshViewBackgroundDrawableRes */ public void setRefreshViewBackgroundDrawableRes(@DrawableRes int refreshViewBackgroundDrawableRes) { mRefreshViewBackgroundDrawableRes = refreshViewBackgroundDrawableRes; } /** * 获取顶部未满足下拉刷新条件时回弹到初始状态、满足刷新条件时回弹到正在刷新状态、刷新完毕后回弹到初始状态的动画时间,默认为500毫秒 * * @return */ public int getTopAnimDuration() { return mTopAnimDuration; } /** * 设置顶部未满足下拉刷新条件时回弹到初始状态、满足刷新条件时回弹到正在刷新状态、刷新完毕后回弹到初始状态的动画时间,默认为300毫秒 * * @param topAnimDuration */ public void setTopAnimDuration(int topAnimDuration) { mTopAnimDuration = topAnimDuration; } /** * 获取上拉加载更多控件,如果不喜欢这种上拉刷新风格可重写该方法实现自定义LoadMoreFooterView * * @return */ public View getLoadMoreFooterView() { if (!mIsLoadingMoreEnabled) { return null; } if (mLoadMoreFooterView == null) { mLoadMoreFooterView = View.inflate(mContext, R.layout.loading_alert, null); mLoadMoreFooterView.setBackgroundColor(Color.TRANSPARENT); if (mLoadMoreBackgroundColorRes != -1) { mLoadMoreFooterView.setBackgroundResource(mLoadMoreBackgroundColorRes); } if (mLoadMoreBackgroundDrawableRes != -1) { mLoadMoreFooterView.setBackgroundResource(mLoadMoreBackgroundDrawableRes); } mFooterStatusTv = (TextView) mLoadMoreFooterView.findViewById(R.id.load_content_tv); mFooterChrysanthemumIv = (ImageView) mLoadMoreFooterView.findViewById(R.id.load_pb); mFooterChrysanthemumIv.setBackground(mContext.getDrawable(R.drawable.progress_small)); mFooterChrysanthemumAd = (AnimationDrawable) mFooterChrysanthemumIv.getDrawable(); mFooterStatusTv.setText(mLodingMoreText); } return mLoadMoreFooterView; } /** * 获取头部下拉刷新控件 * * @return */ public abstract View getRefreshHeaderView(); /** * 下拉刷新控件可见时,处理上下拉进度 * * @param scale 下拉过程0 到 1,回弹过程1 到 0,没有加上弹簧距离移动时的比例 * @param moveYDistance 整个下拉刷新控件paddingTop变化的值,如果有弹簧距离,会大于整个下拉刷新控件的高度 */ public abstract void handleScale(float scale, int moveYDistance); /** * 进入到未处理下拉刷新状态 */ public abstract void changeToIdle(); /** * 进入下拉状态 */ public abstract void changeToPullDown(); /** * 进入释放刷新状态 */ public abstract void changeToReleaseRefresh(); /** * 进入正在刷新状态 */ public abstract void changeToRefreshing(); /** * 结束下拉刷新 */ public abstract void onEndRefreshing(); /** * 手指移动距离与下拉刷新控件paddingTop移动距离的比值 * * @return */ public float getPaddingTopScale() { return mPullDistanceScale; } /** * 设置手指移动距离与下拉刷新控件paddingTop移动距离的比值 * * @param pullDistanceScale */ public void setPullDistanceScale(float pullDistanceScale) { mPullDistanceScale = pullDistanceScale; } /** * 下拉刷新控件paddingTop的弹簧距离与下拉刷新控件高度的比值 * * @return */ public float getSpringDistanceScale() { return mSpringDistanceScale; } /** * 设置下拉刷新控件paddingTop的弹簧距离与下拉刷新控件高度的比值,不能小于0,如果刷新控件比较高,建议将该值设置小一些 * * @param springDistanceScale */ public void setSpringDistanceScale(float springDistanceScale) { if (springDistanceScale < 0) { throw new RuntimeException("下拉刷新控件paddingTop的弹簧距离与下拉刷新控件高度的比值springDistanceScale不能小于0"); } mSpringDistanceScale = springDistanceScale; } /** * 是处于能够进入刷新状态 * * @return */ public boolean canChangeToRefreshingStatus() { return false; } /** * 进入加载更多状态 */ public void changeToLoadingMore() { if (mIsLoadingMoreEnabled && mFooterChrysanthemumAd != null) { mFooterChrysanthemumAd.start(); } } /** * 结束上拉加载更多 */ public void onEndLoadingMore() { if (mIsLoadingMoreEnabled && mFooterChrysanthemumAd != null) { mFooterChrysanthemumAd.stop(); } } /** * 获取下拉刷新控件的高度,如果初始化时的高度和最后展开的最大高度不一致,需重写该方法返回最大高度 * * @return */ public int getRefreshHeaderViewHeight() { if (mRefreshHeaderView != null) { // 测量下拉刷新控件的高度 mRefreshHeaderView.measure(0, 0); return mRefreshHeaderView.getMeasuredHeight(); } return 0; } /** * 改变整个下拉刷新头部控件移动一定的距离(带动画),自定义刷新控件进入刷新状态前后的高度有变化时可以使用该方法(参考BGAStickinessRefreshView) * * @param distance */ public void startChangeWholeHeaderViewPaddingTop(int distance) { mRefreshLayout.startChangeWholeHeaderViewPaddingTop(distance); } /** * 设置下拉刷新上拉加载更多控件,该方法是设置BGARefreshViewHolder给BGARefreshLayout时由BGARefreshLayout调用 * * @param refreshLayout */ public void setRefreshLayout(BGARefreshLayout refreshLayout) { mRefreshLayout = refreshLayout; } } app/src/main/java/com/hdl/photovoltaic/widget/refreshlayout/BGAStickyNavLayout.java
New file @@ -0,0 +1,520 @@ /** * Copyright 2015 bingoogolapple * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hdl.photovoltaic.widget.refreshlayout; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.webkit.WebView; import android.widget.AbsListView; import android.widget.LinearLayout; import android.widget.OverScroller; import android.widget.ScrollView; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; /** * 作者:王浩 邮件:bingoogolapple@gmail.com * 创建时间:15/10/28 上午2:32 * 描述: */ public class BGAStickyNavLayout extends LinearLayout { private View mHeaderView; private View mNavView; private View mContentView; private View mDirectNormalView; private RecyclerView mDirectRecyclerView; private AbsListView mDirectAbsListView; private ScrollView mDirectScrollView; private WebView mDirectWebView; private ViewPager mDirectViewPager; private View mNestedContentView; private View mNestedNormalView; private RecyclerView mNestedRecyclerView; private AbsListView mNestedAbsListView; private ScrollView mNestedScrollView; private WebView mNestedWebView; private OverScroller mOverScroller; private VelocityTracker mVelocityTracker; private int mTouchSlop; private int mMaximumVelocity; private int mMinimumVelocity; private boolean mIsInControl = true; private float mLastDispatchY; private float mLastTouchY; public BGARefreshLayout mRefreshLayout; public BGAStickyNavLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context) { setOrientation(VERTICAL); mOverScroller = new OverScroller(context); final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledTouchSlop(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); } @Override public void setOrientation(int orientation) { if (VERTICAL == orientation) { super.setOrientation(VERTICAL); } } @Override public void onFinishInflate() { super.onFinishInflate(); if (getChildCount() != 3) { throw new IllegalStateException(BGAStickyNavLayout.class.getSimpleName() + "必须有且只有三个子控件"); } mHeaderView = getChildAt(0); mNavView = getChildAt(1); mContentView = getChildAt(2); if (mContentView instanceof AbsListView) { mDirectAbsListView = (AbsListView) mContentView; mDirectAbsListView.setOnScrollListener(mLvOnScrollListener); } else if (mContentView instanceof RecyclerView) { mDirectRecyclerView = (RecyclerView) mContentView; mDirectRecyclerView.addOnScrollListener(mRvOnScrollListener); } else if (mContentView instanceof ScrollView) { mDirectScrollView = (ScrollView) mContentView; } else if (mContentView instanceof WebView) { mDirectWebView = (WebView) mContentView; } else if (mContentView instanceof ViewPager) { mDirectViewPager = (ViewPager) mContentView; mDirectViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { regetNestedContentView(); } }); } else { mDirectNormalView = mContentView; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); measureChild(mContentView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec) - getNavViewHeight(), MeasureSpec.EXACTLY)); } @Override public void computeScroll() { if (mOverScroller.computeScrollOffset()) { scrollTo(0, mOverScroller.getCurrY()); invalidate(); } } public void fling(int velocityY) { mOverScroller.fling(0, getScrollY(), 0, velocityY, 0, 0, 0, getHeaderViewHeight()); invalidate(); } @Override public void scrollTo(int x, int y) { if (y < 0) { y = 0; } int headerViewHeight = getHeaderViewHeight(); if (y > headerViewHeight) { y = headerViewHeight; } if (y != getScrollY()) { super.scrollTo(x, y); } } /** * 获取头部视图高度,包括topMargin和bottomMargin * * @return */ private int getHeaderViewHeight() { MarginLayoutParams layoutParams = (MarginLayoutParams) mHeaderView.getLayoutParams(); return mHeaderView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin; } /** * 获取导航视图的高度,包括topMargin和bottomMargin * * @return */ private int getNavViewHeight() { MarginLayoutParams layoutParams = (MarginLayoutParams) mNavView.getLayoutParams(); return mNavView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin; } /** * 头部视图是否已经完全隐藏 * * @return */ private boolean isHeaderViewCompleteInvisible() { // 0表示x,1表示y int[] location = new int[2]; getLocationOnScreen(location); int contentOnScreenTopY = location[1] + getPaddingTop(); mNavView.getLocationOnScreen(location); MarginLayoutParams params = (MarginLayoutParams) mNavView.getLayoutParams(); int navViewTopOnScreenY = location[1] - params.topMargin; if (navViewTopOnScreenY == contentOnScreenTopY) { return true; } else { return false; } } private void initVelocityTrackerIfNotExists() { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } } private void recycleVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { float currentTouchY = ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastDispatchY = currentTouchY; break; case MotionEvent.ACTION_MOVE: float differentY = currentTouchY - mLastDispatchY; mLastDispatchY = currentTouchY; if (isContentViewToTop() && isHeaderViewCompleteInvisible()) { if (differentY >= 0 && !mIsInControl) { mIsInControl = true; return resetDispatchTouchEvent(ev); } if (differentY <= 0 && mIsInControl) { mIsInControl = false; return resetDispatchTouchEvent(ev); } } break; } return super.dispatchTouchEvent(ev); } private boolean resetDispatchTouchEvent(MotionEvent ev) { MotionEvent newEvent = MotionEvent.obtain(ev); ev.setAction(MotionEvent.ACTION_CANCEL); dispatchTouchEvent(ev); newEvent.setAction(MotionEvent.ACTION_DOWN); return dispatchTouchEvent(newEvent); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { float currentTouchY = ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastTouchY = currentTouchY; break; case MotionEvent.ACTION_MOVE: float differentY = currentTouchY - mLastTouchY; if (Math.abs(differentY) > mTouchSlop) { if (!isHeaderViewCompleteInvisible() || (isContentViewToTop() && isHeaderViewCompleteInvisible() && mIsInControl)) { mLastTouchY = currentTouchY; return true; } } break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(event); float currentTouchY = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (!mOverScroller.isFinished()) { mOverScroller.abortAnimation(); } mLastTouchY = currentTouchY; break; case MotionEvent.ACTION_MOVE: float differentY = currentTouchY - mLastTouchY; mLastTouchY = currentTouchY; if (Math.abs(differentY) > 0) { scrollBy(0, (int) -differentY); } break; case MotionEvent.ACTION_CANCEL: recycleVelocityTracker(); if (!mOverScroller.isFinished()) { mOverScroller.abortAnimation(); } break; case MotionEvent.ACTION_UP: mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) mVelocityTracker.getYVelocity(); if ((Math.abs(initialVelocity) > mMinimumVelocity)) { fling(-initialVelocity); } recycleVelocityTracker(); break; } return true; } public boolean isContentViewToTop() { if (mDirectNormalView != null) { return true; } if (BGARefreshScrollingUtil.isScrollViewOrWebViewToTop(mDirectWebView)) { return true; } if (BGARefreshScrollingUtil.isScrollViewOrWebViewToTop(mDirectScrollView)) { return true; } if (BGARefreshScrollingUtil.isAbsListViewToTop(mDirectAbsListView)) { return true; } if (BGARefreshScrollingUtil.isRecyclerViewToTop(mDirectRecyclerView)) { return true; } if (mDirectViewPager != null) { return isViewPagerContentViewToTop(); } return false; } private boolean isViewPagerContentViewToTop() { if (mNestedContentView == null) { regetNestedContentView(); } if (mDirectNormalView != null) { return true; } if (BGARefreshScrollingUtil.isScrollViewOrWebViewToTop(mNestedWebView)) { return true; } if (BGARefreshScrollingUtil.isScrollViewOrWebViewToTop(mNestedScrollView)) { return true; } if (BGARefreshScrollingUtil.isAbsListViewToTop(mNestedAbsListView)) { return true; } if (BGARefreshScrollingUtil.isRecyclerViewToTop(mNestedRecyclerView)) { return true; } return false; } /** * 重新获取嵌套的内容视图 */ private void regetNestedContentView() { int currentItem = mDirectViewPager.getCurrentItem(); PagerAdapter adapter = mDirectViewPager.getAdapter(); if (adapter instanceof FragmentPagerAdapter || adapter instanceof FragmentStatePagerAdapter) { Fragment item = (Fragment) adapter.instantiateItem(mDirectViewPager, currentItem); mNestedContentView = item.getView(); // 清空之前的 mNestedNormalView = null; mNestedAbsListView = null; mNestedRecyclerView = null; mNestedScrollView = null; mNestedWebView = null; if (mNestedContentView instanceof AbsListView) { mNestedAbsListView = (AbsListView) mNestedContentView; mNestedAbsListView.setOnScrollListener(mLvOnScrollListener); if (!isHeaderViewCompleteInvisible()) { mNestedAbsListView.setSelection(0); } } else if (mNestedContentView instanceof RecyclerView) { mNestedRecyclerView = (RecyclerView) mNestedContentView; mNestedRecyclerView.removeOnScrollListener(mRvOnScrollListener); mNestedRecyclerView.addOnScrollListener(mRvOnScrollListener); if (!isHeaderViewCompleteInvisible()) { mNestedRecyclerView.scrollToPosition(0); } } else if (mNestedContentView instanceof ScrollView) { mNestedScrollView = (ScrollView) mNestedContentView; if (!isHeaderViewCompleteInvisible()) { mNestedScrollView.scrollTo(mNestedScrollView.getScrollX(), 0); } } else if (mNestedContentView instanceof WebView) { mNestedWebView = (WebView) mNestedContentView; if (!isHeaderViewCompleteInvisible()) { mNestedWebView.scrollTo(mNestedWebView.getScrollX(), 0); } } else { mNestedNormalView = mNestedContentView; } } else { throw new IllegalStateException(BGAStickyNavLayout.class.getSimpleName() + "的第三个子控件为ViewPager时,其adapter必须是FragmentPagerAdapter或者FragmentStatePagerAdapter"); } } public void setRefreshLayout(BGARefreshLayout refreshLayout) { mRefreshLayout = refreshLayout; } private RecyclerView.OnScrollListener mRvOnScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if ((newState == RecyclerView.SCROLL_STATE_IDLE || newState == RecyclerView.SCROLL_STATE_SETTLING) && mRefreshLayout != null && mRefreshLayout.shouldHandleRecyclerViewLoadingMore(recyclerView)) { mRefreshLayout.beginLoadingMore(); } } }; private AbsListView.OnScrollListener mLvOnScrollListener = new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView absListView, int scrollState) { if ((scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_FLING) && mRefreshLayout != null && mRefreshLayout.shouldHandleAbsListViewLoadingMore(absListView)) { mRefreshLayout.beginLoadingMore(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }; public boolean shouldHandleLoadingMore() { if (mRefreshLayout == null) { return false; } if (mDirectNormalView != null) { return true; } if (BGARefreshScrollingUtil.isWebViewToBottom(mDirectWebView)) { return true; } if (BGARefreshScrollingUtil.isScrollViewToBottom(mDirectScrollView)) { return true; } if (mDirectAbsListView != null) { return mRefreshLayout.shouldHandleAbsListViewLoadingMore(mDirectAbsListView); } if (mDirectRecyclerView != null) { return mRefreshLayout.shouldHandleRecyclerViewLoadingMore(mDirectRecyclerView); } if (mDirectViewPager != null) { if (mNestedContentView == null) { regetNestedContentView(); } if (mNestedNormalView != null) { return true; } if (BGARefreshScrollingUtil.isWebViewToBottom(mNestedWebView)) { return true; } if (BGARefreshScrollingUtil.isScrollViewToBottom(mNestedScrollView)) { return true; } if (mNestedAbsListView != null) { return mRefreshLayout.shouldHandleAbsListViewLoadingMore(mNestedAbsListView); } if (mNestedRecyclerView != null) { return mRefreshLayout.shouldHandleRecyclerViewLoadingMore(mNestedRecyclerView); } } return false; } public void scrollToBottom() { BGARefreshScrollingUtil.scrollToBottom(mDirectScrollView); BGARefreshScrollingUtil.scrollToBottom(mDirectRecyclerView); BGARefreshScrollingUtil.scrollToBottom(mDirectAbsListView); if (mDirectViewPager != null) { if (mNestedContentView == null) { regetNestedContentView(); } BGARefreshScrollingUtil.scrollToBottom(mNestedScrollView); BGARefreshScrollingUtil.scrollToBottom(mNestedRecyclerView); BGARefreshScrollingUtil.scrollToBottom(mNestedAbsListView); } } } app/src/main/java/com/hdl/photovoltaic/widget/refreshlayout/HDLRefreshView.java
New file @@ -0,0 +1,127 @@ /** * Copyright 2015 bingoogolapple * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hdl.photovoltaic.widget.refreshlayout; import android.content.Context; import android.graphics.drawable.AnimationDrawable; import android.util.AttributeSet; import android.widget.ImageView; import android.widget.RelativeLayout; import androidx.annotation.DrawableRes; import androidx.core.view.ViewCompat; import com.hdl.photovoltaic.R; public class HDLRefreshView extends RelativeLayout { private ImageView mPullDownView; private ImageView mReleaseRefreshingView; private AnimationDrawable mChangeToReleaseRefreshAd; private AnimationDrawable mRefreshingAd; private int mChangeToReleaseRefreshAnimResId; private int mRefreshingAnimResId; public HDLRefreshView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { super.onFinishInflate(); mPullDownView = (ImageView) findViewById(R.id.iv_pull_down); mReleaseRefreshingView = (ImageView) findViewById(R.id.iv_release_refreshing); } public void setPullDownImageResource(@DrawableRes int resId) { mPullDownView.setImageResource(resId); } public void setChangeToReleaseRefreshAnimResId(@DrawableRes int resId) { mChangeToReleaseRefreshAnimResId = resId; mReleaseRefreshingView.setImageResource(mChangeToReleaseRefreshAnimResId); } public void setRefreshingAnimResId(@DrawableRes int resId) { mRefreshingAnimResId = resId; } public void handleScale(float scale) { scale = 0.1f + 0.9f * scale; ViewCompat.setPivotX(mPullDownView, mPullDownView.getWidth() * 0.5f); ViewCompat.setPivotY(mPullDownView, mPullDownView.getHeight() * 0.5f); ViewCompat.setScaleX(mPullDownView, scale); ViewCompat.setScaleY(mPullDownView, scale); } public void changeToIdle() { stopChangeToReleaseRefreshAd(); stopRefreshingAd(); mPullDownView.setVisibility(VISIBLE); mReleaseRefreshingView.setVisibility(INVISIBLE); } public void changeToPullDown() { mPullDownView.setVisibility(VISIBLE); mReleaseRefreshingView.setVisibility(INVISIBLE); } public void changeToReleaseRefresh() { mReleaseRefreshingView.setImageResource(mChangeToReleaseRefreshAnimResId); mChangeToReleaseRefreshAd = (AnimationDrawable) mReleaseRefreshingView.getDrawable(); mReleaseRefreshingView.setVisibility(VISIBLE); mPullDownView.setVisibility(INVISIBLE); mChangeToReleaseRefreshAd.start(); } public void changeToRefreshing() { stopChangeToReleaseRefreshAd(); mReleaseRefreshingView.setImageResource(mRefreshingAnimResId); mRefreshingAd = (AnimationDrawable) mReleaseRefreshingView.getDrawable(); mReleaseRefreshingView.setVisibility(VISIBLE); mPullDownView.setVisibility(INVISIBLE); mRefreshingAd.start(); } public void onEndRefreshing() { stopChangeToReleaseRefreshAd(); stopRefreshingAd(); } private void stopRefreshingAd() { if (mRefreshingAd != null) { mRefreshingAd.stop(); mRefreshingAd = null; } } private void stopChangeToReleaseRefreshAd() { if (mChangeToReleaseRefreshAd != null) { mChangeToReleaseRefreshAd.stop(); mChangeToReleaseRefreshAd = null; } } } app/src/main/java/com/hdl/photovoltaic/widget/refreshlayout/HDLRefreshViewHolder.java
New file @@ -0,0 +1,131 @@ /** * Copyright 2015 bingoogolapple * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hdl.photovoltaic.widget.refreshlayout; import android.content.Context; import android.graphics.Color; import android.view.View; import androidx.annotation.DrawableRes; import com.hdl.photovoltaic.R; public class HDLRefreshViewHolder extends BGARefreshViewHolder { private HDLRefreshView mRefreshView; private int mPullDownImageResId = -1; private int mChangeToReleaseRefreshAnimResId = -1; private int mRefreshingAnimResId = -1; /** * @param context * @param isLoadingMoreEnabled 上拉加载更多是否可用 */ public HDLRefreshViewHolder(Context context, boolean isLoadingMoreEnabled) { super(context, isLoadingMoreEnabled); } /** * 设置下拉过程中的图片资源 * * @param resId */ public void setPullDownImageResource(@DrawableRes int resId) { mPullDownImageResId = resId; } /** * 设置进入释放刷新状态时的动画资源 * * @param resId */ public void setChangeToReleaseRefreshAnimResId(@DrawableRes int resId) { mChangeToReleaseRefreshAnimResId = resId; } /** * 设置正在刷新时的动画资源 * * @param resId */ public void setRefreshingAnimResId(@DrawableRes int resId) { mRefreshingAnimResId = resId; } @Override public View getRefreshHeaderView() { if (mRefreshHeaderView == null) { mRefreshHeaderView = View.inflate(mContext, R.layout.view_refresh_header, null); mRefreshHeaderView.setBackgroundColor(Color.TRANSPARENT); if (mRefreshViewBackgroundColorRes != -1) { mRefreshHeaderView.setBackgroundResource(mRefreshViewBackgroundColorRes); } if (mRefreshViewBackgroundDrawableRes != -1) { mRefreshHeaderView.setBackgroundResource(mRefreshViewBackgroundDrawableRes); } mRefreshView = (HDLRefreshView) mRefreshHeaderView.findViewById(R.id.refreshView); if (mPullDownImageResId != -1) { mRefreshView.setPullDownImageResource(mPullDownImageResId); } else { throw new RuntimeException("请调用" + HDLRefreshViewHolder.class.getSimpleName() + "的setPullDownImageResource方法设置下拉过程中的图片资源"); } if (mChangeToReleaseRefreshAnimResId != -1) { mRefreshView.setChangeToReleaseRefreshAnimResId(mChangeToReleaseRefreshAnimResId); } else { throw new RuntimeException("请调用" + HDLRefreshViewHolder.class.getSimpleName() + "的setChangeToReleaseRefreshAnimResId方法设置进入释放刷新状态时的动画资源"); } if (mRefreshingAnimResId != -1) { mRefreshView.setRefreshingAnimResId(mRefreshingAnimResId); } else { throw new RuntimeException("请调用" + HDLRefreshViewHolder.class.getSimpleName() + "的setRefreshingAnimResId方法设置正在刷新时的动画资源"); } } return mRefreshHeaderView; } @Override public void handleScale(float scale, int moveYDistance) { if (scale <= 1.0f) { mRefreshView.handleScale(scale); } } @Override public void changeToIdle() { mRefreshView.changeToIdle(); } @Override public void changeToPullDown() { mRefreshView.changeToPullDown(); } @Override public void changeToReleaseRefresh() { mRefreshView.changeToReleaseRefresh(); } @Override public void changeToRefreshing() { mRefreshView.changeToRefreshing(); } @Override public void onEndRefreshing() { mRefreshView.onEndRefreshing(); } } app/src/main/java/com/hdl/photovoltaic/widget/refreshlayout/NestedScrollLayout.java
New file @@ -0,0 +1,152 @@ package com.hdl.photovoltaic.widget.refreshlayout; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.widget.NestedScrollView; import androidx.recyclerview.widget.RecyclerView; import io.dcloud.weex.FlingHelper; public class NestedScrollLayout extends NestedScrollView { private View topView; private ViewGroup contentView; private static final String TAG = "NestedScrollLayout"; public NestedScrollLayout(Context context) { this(context, null); init(); } public NestedScrollLayout(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); init(); } public NestedScrollLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); init(); } public NestedScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); init(); } private FlingHelper mFlingHelper; int totalDy = 0; /** * 用于判断RecyclerView是否在fling */ boolean isStartFling = false; /** * 记录当前滑动的y轴加速度 */ private int velocityY = 0; private void init() { mFlingHelper = new FlingHelper(getContext()); setOnScrollChangeListener(new View.OnScrollChangeListener() { @Override public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { if (isStartFling) { totalDy = 0; isStartFling = false; } if (scrollY == 0) { Log.e(TAG, "TOP SCROLL"); // refreshLayout.setEnabled(true); } if (scrollY == (getChildAt(0).getMeasuredHeight() - v.getMeasuredHeight())) { Log.e(TAG, "BOTTOM SCROLL"); dispatchChildFling(); } //在RecyclerView fling情况下,记录当前RecyclerView在y轴的偏移 totalDy += scrollY - oldScrollY; } }); } private void dispatchChildFling() { if (velocityY != 0) { Double splineFlingDistance = mFlingHelper.getSplineFlingDistance(velocityY); if (splineFlingDistance > totalDy) { childFling(mFlingHelper.getVelocityByDistance(splineFlingDistance - Double.valueOf(totalDy))); } } totalDy = 0; velocityY = 0; } private void childFling(int velY) { RecyclerView childRecyclerView = getChildRecyclerView(contentView); if (childRecyclerView != null) { childRecyclerView.fling(0, velY); } } @Override public void fling(int velocityY) { super.fling(velocityY); if (velocityY <= 0) { this.velocityY = 0; } else { isStartFling = true; this.velocityY = velocityY; } } @Override protected void onFinishInflate() { super.onFinishInflate(); topView = ((ViewGroup) getChildAt(0)).getChildAt(0); contentView = (ViewGroup) ((ViewGroup) getChildAt(0)).getChildAt(1); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 调整contentView的高度为父容器高度,使之填充布局,避免父容器滚动后出现空白 super.onMeasure(widthMeasureSpec, heightMeasureSpec); ViewGroup.LayoutParams lp = contentView.getLayoutParams(); lp.height = getMeasuredHeight(); contentView.setLayoutParams(lp); } /** * 解决滑动冲突:RecyclerView在滑动之前会问下父布局是否需要拦截,父布局使用此方法 */ @Override public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) { // Log.e("NestedScrollLayout", getScrollY() + "::onNestedPreScroll::" + topView.getMeasuredHeight() + "::dy::" + dy); // 向上滑动。若当前topview可见,需要将topview滑动至不可见 boolean hideTop = dy > 0 && getScrollY() < topView.getMeasuredHeight(); if (hideTop) { scrollBy(0, dy); // 这个是被消费的距离,如果没有会被重复消费,现象是父布局与子布局同时滑动 consumed[1] = dy; } } private RecyclerView getChildRecyclerView(ViewGroup viewGroup) { for (int i = 0; i < viewGroup.getChildCount(); i++) { View view = viewGroup.getChildAt(i); if (view instanceof RecyclerView) { return (RecyclerView) viewGroup.getChildAt(i); } else if (viewGroup.getChildAt(i) instanceof ViewGroup) { ViewGroup childRecyclerView = getChildRecyclerView((ViewGroup) viewGroup.getChildAt(i)); if (childRecyclerView instanceof RecyclerView) { return (RecyclerView) childRecyclerView; } } continue; } return null; } } app/src/main/res/drawable-xhdpi/loading_gif.gifapp/src/main/res/drawable/loading_44.png
app/src/main/res/drawable/loading_gif.pngapp/src/main/res/drawable/loading_refresh.xml
New file @@ -0,0 +1,136 @@ <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@mipmap/loading_1" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_2"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_3"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_4"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_5"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_6"--> <!-- android:duration="100" />--> <item android:drawable="@mipmap/loading_7" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_8"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_9"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_10"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_11"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_12"--> <!-- android:duration="100" />--> <item android:drawable="@mipmap/loading_13" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_14"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_15"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_16"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_17"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_18"--> <!-- android:duration="100" />--> <item android:drawable="@mipmap/loading_19" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_20"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_21"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_22"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_23"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_24"--> <!-- android:duration="100" />--> <item android:drawable="@mipmap/loading_25" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_26"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_27"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_28"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_29"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_30"--> <!-- android:duration="100" />--> <item android:drawable="@mipmap/loading_31" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_32"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_33"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_34"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_35"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_36"--> <!-- android:duration="100" />--> <item android:drawable="@mipmap/loading_37" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_38"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_39"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_40"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_41"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_42"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_43"--> <!-- android:duration="150" />--> <item android:drawable="@mipmap/loading_44" android:duration="100" /> </animation-list> app/src/main/res/drawable/loading_refresh_end.xml
New file @@ -0,0 +1,138 @@ <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@mipmap/loading_44" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_2"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_3"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_4"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_5"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_6"--> <!-- android:duration="100" />--> <item android:drawable="@mipmap/loading_37" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_8"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_9"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_10"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_11"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_12"--> <!-- android:duration="100" />--> <item android:drawable="@mipmap/loading_31" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_14"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_15"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_16"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_17"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_18"--> <!-- android:duration="100" />--> <item android:drawable="@mipmap/loading_25" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_20"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_21"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_22"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_23"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_24"--> <!-- android:duration="100" />--> <item android:drawable="@mipmap/loading_19" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_26"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_27"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_28"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_29"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_30"--> <!-- android:duration="100" />--> <item android:drawable="@mipmap/loading_31" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_32"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_33"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_34"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_35"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_36"--> <!-- android:duration="100" />--> <item android:drawable="@mipmap/loading_37" android:duration="100" /> <!-- <item--> <!-- android:drawable="@mipmap/loading_38"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_39"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_40"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_41"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_42"--> <!-- android:duration="100" />--> <!-- <item--> <!-- android:drawable="@mipmap/loading_43"--> <!-- android:duration="150" />--> <item android:drawable="@mipmap/loading_44" android:duration="100" /> </animation-list> app/src/main/res/layout/fragment_home_page.xml
@@ -37,8 +37,8 @@ android:src="@drawable/add" /> </RelativeLayout> <androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:id="@+id/srl" <com.hdl.photovoltaic.widget.refreshlayout.BGARefreshLayout android:id="@+id/rsl" android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginTop="@dimen/dp_10" @@ -952,7 +952,21 @@ </ScrollView> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </com.hdl.photovoltaic.widget.refreshlayout.BGARefreshLayout> <!-- <androidx.constraintlayout.widget.Guideline--> <!-- android:id="@+id/guideline2"--> <!-- android:layout_width="wrap_content"--> <!-- android:layout_height="wrap_content"--> <!-- android:orientation="horizontal"--> <!-- app:layout_constraintGuide_begin="20dp" />--> <!-- <androidx.constraintlayout.widget.Guideline--> <!-- android:id="@+id/guideline3"--> <!-- android:layout_width="wrap_content"--> <!-- android:layout_height="wrap_content"--> <!-- android:orientation="vertical"--> <!-- app:layout_constraintGuide_begin="20dp" />--> </androidx.constraintlayout.widget.ConstraintLayout> app/src/main/res/layout/view_refresh_header.xml
New file @@ -0,0 +1,29 @@ <?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="wrap_content" android:gravity="center" android:orientation="vertical"> <com.hdl.photovoltaic.widget.refreshlayout.HDLRefreshView android:id="@+id/refreshView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:layout_marginBottom="5dp"> <ImageView android:id="@+id/iv_pull_down" android:layout_width="@dimen/dp_60" android:layout_height="@dimen/dp_36" android:layout_centerInParent="true" /> <ImageView android:id="@+id/iv_release_refreshing" android:layout_width="@dimen/dp_60" android:layout_height="@dimen/dp_36" android:layout_centerInParent="true" android:visibility="invisible" /> </com.hdl.photovoltaic.widget.refreshlayout.HDLRefreshView> </LinearLayout> app/src/main/res/mipmap-xhdpi/loading_1.png
app/src/main/res/mipmap-xhdpi/loading_10.png
app/src/main/res/mipmap-xhdpi/loading_11.png
app/src/main/res/mipmap-xhdpi/loading_12.png
app/src/main/res/mipmap-xhdpi/loading_13.png
app/src/main/res/mipmap-xhdpi/loading_14.png
app/src/main/res/mipmap-xhdpi/loading_15.png
app/src/main/res/mipmap-xhdpi/loading_16.png
app/src/main/res/mipmap-xhdpi/loading_17.png
app/src/main/res/mipmap-xhdpi/loading_18.png
app/src/main/res/mipmap-xhdpi/loading_19.png
app/src/main/res/mipmap-xhdpi/loading_2.png
app/src/main/res/mipmap-xhdpi/loading_20.png
app/src/main/res/mipmap-xhdpi/loading_21.png
app/src/main/res/mipmap-xhdpi/loading_22.png
app/src/main/res/mipmap-xhdpi/loading_23.png
app/src/main/res/mipmap-xhdpi/loading_24.png
app/src/main/res/mipmap-xhdpi/loading_25.png
app/src/main/res/mipmap-xhdpi/loading_26.png
app/src/main/res/mipmap-xhdpi/loading_27.png
app/src/main/res/mipmap-xhdpi/loading_28.png
app/src/main/res/mipmap-xhdpi/loading_29.png
app/src/main/res/mipmap-xhdpi/loading_3.png
app/src/main/res/mipmap-xhdpi/loading_30.png
app/src/main/res/mipmap-xhdpi/loading_31.png
app/src/main/res/mipmap-xhdpi/loading_32.png
app/src/main/res/mipmap-xhdpi/loading_33.png
app/src/main/res/mipmap-xhdpi/loading_34.png
app/src/main/res/mipmap-xhdpi/loading_35.png
app/src/main/res/mipmap-xhdpi/loading_36.png
app/src/main/res/mipmap-xhdpi/loading_37.png
app/src/main/res/mipmap-xhdpi/loading_38.png
app/src/main/res/mipmap-xhdpi/loading_39.png
app/src/main/res/mipmap-xhdpi/loading_4.png
app/src/main/res/mipmap-xhdpi/loading_40.png
app/src/main/res/mipmap-xhdpi/loading_41.png
app/src/main/res/mipmap-xhdpi/loading_42.png
app/src/main/res/mipmap-xhdpi/loading_43.png
app/src/main/res/mipmap-xhdpi/loading_44.png
app/src/main/res/mipmap-xhdpi/loading_5.png
app/src/main/res/mipmap-xhdpi/loading_6.png
app/src/main/res/mipmap-xhdpi/loading_7.png
app/src/main/res/mipmap-xhdpi/loading_8.png
app/src/main/res/mipmap-xhdpi/loading_9.png