├── .DS_Store ├── readme.md └── widgetpro ├── .DS_Store ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── .DS_Store ├── androidTest └── java │ └── com │ └── example │ └── widgetpro │ └── ExampleInstrumentedTest.java ├── main ├── .DS_Store ├── AndroidManifest.xml ├── java │ ├── .DS_Store │ ├── com │ │ └── example │ │ │ └── widgetpro │ │ │ └── MainActivity.java │ └── gzoomswiperefresh │ │ ├── ApiShowFragment.java │ │ ├── CircleImageView.java │ │ ├── GZoomSwifrefresh.java │ │ ├── MainActivity.java │ │ ├── MaterialProgressDrawable.java │ │ ├── TabFragmentAdapter.java │ │ └── TempAdapter.java └── res │ ├── drawable │ ├── ic_favorite_border_black_24dp.xml │ ├── ic_slide_menu_avatar_no_login.png │ └── start.jpg │ ├── layout │ ├── activity_main.xml │ ├── app_bar_gzoom.xml │ ├── app_bar_main.xml │ ├── nav_header_main.xml │ ├── own_main.xml │ ├── temp_fag.xml │ └── temp_item.xml │ ├── menu │ └── drawer.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── test └── java └── com └── example └── widgetpro └── ExampleUnitTest.java /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SGZoom/DailyWidget/d5023c52639eb1c64e630ace6e12298e930ae30f/.DS_Store -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 控件改造 # 2 | 3 | - **改造官方控件SwipeRefreshLayout** 4 | 通过改造,熟悉NestedScrolling嵌套滑动机制 5 | 效果如下: 6 | ![](http://img.blog.csdn.net/20170226112533096?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR2V0X1pvb20=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 7 | 8 | 博客地址: 9 | [http://blog.csdn.net/Get_Zoom/article/details/57400231](http://blog.csdn.net/Get_Zoom/article/details/57400231 "利用NestedScrolling机制改造SwipeRefreshLayout") 10 | -------------------------------------------------------------------------------- /widgetpro/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SGZoom/DailyWidget/d5023c52639eb1c64e630ace6e12298e930ae30f/widgetpro/.DS_Store -------------------------------------------------------------------------------- /widgetpro/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /widgetpro/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.example.widgetpro" 9 | minSdkVersion 15 10 | targetSdkVersion 25 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(include: ['*.jar'], dir: 'libs') 27 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 28 | exclude group: 'com.android.support', module: 'support-annotations' 29 | }) 30 | compile 'com.android.support:appcompat-v7:25.1.1' 31 | testCompile 'junit:junit:4.12' 32 | compile 'com.jakewharton:butterknife:7.0.1' 33 | compile 'com.android.support:recyclerview-v7:25.1.1' 34 | compile 'com.android.support:appcompat-v7:25.1.1' 35 | compile 'com.android.support:design:25.1.1' 36 | } 37 | -------------------------------------------------------------------------------- /widgetpro/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\adt0525\adt-bundle-windows-x86_64-20140702\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /widgetpro/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SGZoom/DailyWidget/d5023c52639eb1c64e630ace6e12298e930ae30f/widgetpro/src/.DS_Store -------------------------------------------------------------------------------- /widgetpro/src/androidTest/java/com/example/widgetpro/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.widgetpro; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.example.widgetpro", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /widgetpro/src/main/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SGZoom/DailyWidget/d5023c52639eb1c64e630ace6e12298e930ae30f/widgetpro/src/main/.DS_Store -------------------------------------------------------------------------------- /widgetpro/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /widgetpro/src/main/java/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SGZoom/DailyWidget/d5023c52639eb1c64e630ace6e12298e930ae30f/widgetpro/src/main/java/.DS_Store -------------------------------------------------------------------------------- /widgetpro/src/main/java/com/example/widgetpro/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.widgetpro; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | public class MainActivity extends AppCompatActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_main); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /widgetpro/src/main/java/gzoomswiperefresh/ApiShowFragment.java: -------------------------------------------------------------------------------- 1 | package gzoomswiperefresh; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.os.Message; 8 | import android.support.annotation.Nullable; 9 | import android.support.design.widget.Snackbar; 10 | import android.support.v4.app.Fragment; 11 | import android.support.v4.widget.SwipeRefreshLayout; 12 | import android.support.v7.widget.LinearLayoutManager; 13 | import android.support.v7.widget.RecyclerView; 14 | import android.view.LayoutInflater; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | 18 | 19 | import com.example.widgetpro.R; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | import butterknife.Bind; 26 | import butterknife.ButterKnife; 27 | 28 | 29 | /** 30 | * Created by gzoom on 2016/11/27. 31 | * 演示用fragment 32 | */ 33 | 34 | public class ApiShowFragment extends Fragment { 35 | 36 | @Bind(R.id.tempfrag_swiperefresh) 37 | GZoomSwifrefresh refreshLayout; 38 | 39 | @Bind(R.id.main_recycleview) 40 | RecyclerView recyclerView; 41 | /**context*/ 42 | Context context; 43 | 44 | SwipeRefreshLayout s; 45 | 46 | /**标示个数*/ 47 | private int index=0; 48 | 49 | /**数据源*/ 50 | Listdatas = new ArrayList<>(); 51 | 52 | 53 | /**测试用*/ 54 | TempAdapter tempAdapter; 55 | 56 | Handler hd = new Handler(){ 57 | @Override 58 | public void handleMessage(Message msg) { 59 | addData(); 60 | cancleLoading(); 61 | 62 | } 63 | }; 64 | 65 | @Override 66 | public void onCreate(@Nullable Bundle savedInstanceState) { 67 | super.onCreate(savedInstanceState); 68 | context=this.getActivity(); 69 | } 70 | 71 | @Nullable 72 | @Override 73 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 74 | 75 | View view = inflater.inflate(R.layout.temp_fag,container,false); 76 | ButterKnife.bind(this,view); 77 | initRefreshLayout(); 78 | //初始化recycleview 79 | initRecycleView(); 80 | return view; 81 | } 82 | 83 | 84 | 85 | /**初始化刷新控件*/ 86 | private void initRefreshLayout() { 87 | refreshLayout.setColorSchemeColors(Color.YELLOW,Color.RED,Color.GREEN,Color.BLUE); 88 | refreshLayout.setOnRefreshListener(new GZoomSwifrefresh.OnRefreshListener() { 89 | @Override 90 | public void onRefresh() { 91 | hd.sendEmptyMessageDelayed(0,3000); 92 | } 93 | }); 94 | 95 | refreshLayout.setBottomColorSchemeColors(Color.GREEN,Color.BLUE,Color.YELLOW,Color.RED); 96 | //在这里新增下拉刷新方法 97 | refreshLayout.setOnBottomRefreshListenrer(new GZoomSwifrefresh.OnBottomRefreshListener() { 98 | @Override 99 | public void onBottomRefresh() { 100 | Snackbar.make(recyclerView,"下拉刷新了",Snackbar.LENGTH_SHORT).show(); 101 | hd.sendEmptyMessageDelayed(0,3000); 102 | } 103 | }); 104 | } 105 | 106 | 107 | /**取消加载*/ 108 | public void cancleLoading() 109 | { 110 | if(refreshLayout.isRefreshing()) 111 | refreshLayout.setRefreshing(false); 112 | refreshLayout.setBottomRefreshing(false); 113 | } 114 | 115 | /**初始化recycleview*/ 116 | private void initRecycleView() { 117 | // ListtempS = new ArrayList<>(); 118 | for(int i = 0; i<10 ;i++) 119 | { 120 | datas.add(index+":GZ"); 121 | index++; 122 | } 123 | tempAdapter = new TempAdapter(context,datas); 124 | recyclerView.setAdapter(tempAdapter); 125 | LinearLayoutManager man = new LinearLayoutManager(context); 126 | man.setOrientation(LinearLayoutManager.VERTICAL); 127 | recyclerView.setLayoutManager(man); 128 | 129 | } 130 | /**新增数据*/ 131 | private void addData() 132 | { 133 | for(int i = 0; i<5 ;i++) 134 | { 135 | datas.add(index+":GZ"); 136 | index++; 137 | } 138 | tempAdapter.notifyDataSetChanged(); 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /widgetpro/src/main/java/gzoomswiperefresh/CircleImageView.java: -------------------------------------------------------------------------------- 1 | package gzoomswiperefresh; 2 | 3 | /** 4 | * Created by gzoom on 2017/2/4. 5 | *//* 6 | * Copyright (C) 2014 The Android Open Source Project 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | import android.content.Context; 22 | import android.graphics.Canvas; 23 | import android.graphics.Color; 24 | import android.graphics.Paint; 25 | import android.graphics.RadialGradient; 26 | import android.graphics.Shader; 27 | import android.graphics.drawable.ShapeDrawable; 28 | import android.graphics.drawable.shapes.OvalShape; 29 | import android.support.v4.content.ContextCompat; 30 | import android.support.v4.view.ViewCompat; 31 | import android.view.animation.Animation; 32 | import android.widget.ImageView; 33 | 34 | 35 | /** 36 | * Private class created to work around issues with AnimationListeners being 37 | * called before the animation is actually complete and support shadows on older 38 | * platforms. 39 | */ 40 | class CircleImageView extends ImageView { 41 | 42 | private static final int KEY_SHADOW_COLOR = 0x1E000000; 43 | private static final int FILL_SHADOW_COLOR = 0x3D000000; 44 | // PX 45 | private static final float X_OFFSET = 0f; 46 | private static final float Y_OFFSET = 1.75f; 47 | private static final float SHADOW_RADIUS = 3.5f; 48 | private static final int SHADOW_ELEVATION = 4; 49 | 50 | private Animation.AnimationListener mListener; 51 | int mShadowRadius; 52 | 53 | CircleImageView(Context context, int color) { 54 | super(context); 55 | final float density = getContext().getResources().getDisplayMetrics().density; 56 | final int shadowYOffset = (int) (density * Y_OFFSET); 57 | final int shadowXOffset = (int) (density * X_OFFSET); 58 | 59 | mShadowRadius = (int) (density * SHADOW_RADIUS); 60 | 61 | ShapeDrawable circle; 62 | if (elevationSupported()) { 63 | circle = new ShapeDrawable(new OvalShape()); 64 | ViewCompat.setElevation(this, SHADOW_ELEVATION * density); 65 | } else { 66 | OvalShape oval = new CircleImageView.OvalShadow(mShadowRadius); 67 | circle = new ShapeDrawable(oval); 68 | ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint()); 69 | circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, 70 | KEY_SHADOW_COLOR); 71 | final int padding = mShadowRadius; 72 | // set padding so the inner image sits correctly within the shadow. 73 | setPadding(padding, padding, padding, padding); 74 | } 75 | circle.getPaint().setColor(color); 76 | ViewCompat.setBackground(this, circle); 77 | } 78 | 79 | private boolean elevationSupported() { 80 | return android.os.Build.VERSION.SDK_INT >= 21; 81 | } 82 | 83 | @Override 84 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 85 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 86 | if (!elevationSupported()) { 87 | setMeasuredDimension(getMeasuredWidth() + mShadowRadius * 2, getMeasuredHeight() 88 | + mShadowRadius * 2); 89 | } 90 | } 91 | 92 | public void setAnimationListener(Animation.AnimationListener listener) { 93 | mListener = listener; 94 | } 95 | 96 | @Override 97 | public void onAnimationStart() { 98 | super.onAnimationStart(); 99 | if (mListener != null) { 100 | mListener.onAnimationStart(getAnimation()); 101 | } 102 | } 103 | 104 | @Override 105 | public void onAnimationEnd() { 106 | super.onAnimationEnd(); 107 | if (mListener != null) { 108 | mListener.onAnimationEnd(getAnimation()); 109 | } 110 | } 111 | 112 | /** 113 | * Update the background color of the circle image view. 114 | * 115 | * @param colorRes Id of a color resource. 116 | */ 117 | public void setBackgroundColorRes(int colorRes) { 118 | setBackgroundColor(ContextCompat.getColor(getContext(), colorRes)); 119 | } 120 | 121 | @Override 122 | public void setBackgroundColor(int color) { 123 | if (getBackground() instanceof ShapeDrawable) { 124 | ((ShapeDrawable) getBackground()).getPaint().setColor(color); 125 | } 126 | } 127 | 128 | private class OvalShadow extends OvalShape { 129 | private RadialGradient mRadialGradient; 130 | private Paint mShadowPaint; 131 | 132 | OvalShadow(int shadowRadius) { 133 | super(); 134 | mShadowPaint = new Paint(); 135 | mShadowRadius = shadowRadius; 136 | updateRadialGradient((int) rect().width()); 137 | } 138 | 139 | @Override 140 | protected void onResize(float width, float height) { 141 | super.onResize(width, height); 142 | updateRadialGradient((int) width); 143 | } 144 | 145 | @Override 146 | public void draw(Canvas canvas, Paint paint) { 147 | final int viewWidth = CircleImageView.this.getWidth(); 148 | final int viewHeight = CircleImageView.this.getHeight(); 149 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint); 150 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint); 151 | } 152 | 153 | private void updateRadialGradient(int diameter) { 154 | mRadialGradient = new RadialGradient(diameter / 2, diameter / 2, 155 | mShadowRadius, new int[] { FILL_SHADOW_COLOR, Color.TRANSPARENT }, 156 | null, Shader.TileMode.CLAMP); 157 | mShadowPaint.setShader(mRadialGradient); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /widgetpro/src/main/java/gzoomswiperefresh/GZoomSwifrefresh.java: -------------------------------------------------------------------------------- 1 | package gzoomswiperefresh; 2 | 3 | /** 4 | * Created by gzoom on 2017/2/4. 5 | */ 6 | /* 7 | * Copyright (C) 2013 The Android Open Source Project 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * 21 | * 22 | * 23 | * 24 | */ 25 | 26 | import android.content.Context; 27 | import android.content.res.TypedArray; 28 | import android.os.Handler; 29 | import android.os.Message; 30 | import android.support.annotation.ColorInt; 31 | import android.support.annotation.ColorRes; 32 | import android.support.annotation.Nullable; 33 | import android.support.annotation.VisibleForTesting; 34 | import android.support.v4.content.ContextCompat; 35 | import android.support.v4.view.MotionEventCompat; 36 | import android.support.v4.view.NestedScrollingChild; 37 | import android.support.v4.view.NestedScrollingChildHelper; 38 | import android.support.v4.view.NestedScrollingParent; 39 | import android.support.v4.view.NestedScrollingParentHelper; 40 | import android.support.v4.view.ViewCompat; 41 | import android.util.AttributeSet; 42 | import android.util.DisplayMetrics; 43 | import android.util.Log; 44 | import android.view.MotionEvent; 45 | import android.view.View; 46 | import android.view.ViewConfiguration; 47 | import android.view.ViewGroup; 48 | import android.view.WindowManager; 49 | import android.view.animation.Animation; 50 | import android.view.animation.Animation.AnimationListener; 51 | import android.view.animation.DecelerateInterpolator; 52 | import android.view.animation.Transformation; 53 | import android.widget.AbsListView; 54 | import android.widget.ScrollView; 55 | 56 | /** 57 | * The GZoomSwifrefresh should be used whenever the user can refresh the 58 | * contents of a view via a vertical swipe gesture. The activity that 59 | * instantiates this view should add an OnRefreshListener to be notified 60 | * whenever the swipe to refresh gesture is completed. The GZoomSwifrefresh 61 | * will notify the listener each and every time the gesture is completed again; 62 | * the listener is responsible for correctly determining when to actually 63 | * initiate a refresh of its content. If the listener determines there should 64 | * not be a refresh, it must call setRefreshing(false) to cancel any visual 65 | * indication of a refresh. If an activity wishes to show just the progress 66 | * animation, it should call setRefreshing(true). To disable the gesture and 67 | * progress animation, call setEnabled(false) on the view. 68 | *

69 | * This layout should be made the parent of the view that will be refreshed as a 70 | * result of the gesture and can only support one direct child. This view will 71 | * also be made the target of the gesture and will be forced to match both the 72 | * width and the height supplied in this layout. The GZoomSwifrefresh does not 73 | * provide accessibility events; instead, a menu item must be provided to allow 74 | * refresh of the content wherever this gesture is used. 75 | *

76 | */ 77 | public class GZoomSwifrefresh extends ViewGroup implements NestedScrollingParent, 78 | NestedScrollingChild { 79 | // Maps to ProgressBar.Large style 80 | public static final int LARGE = MaterialProgressDrawable.LARGE; 81 | // Maps to ProgressBar default style 82 | public static final int DEFAULT = MaterialProgressDrawable.DEFAULT; 83 | 84 | @VisibleForTesting 85 | static final int CIRCLE_DIAMETER = 40; 86 | @VisibleForTesting 87 | static final int CIRCLE_DIAMETER_LARGE = 56; 88 | 89 | private static final String LOG_TAG = GZoomSwifrefresh.class.getSimpleName(); 90 | 91 | private static final int MAX_ALPHA = 255; 92 | private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA); 93 | 94 | private static final float DECELERATE_INTERPOLATION_FACTOR = 2f; 95 | private static final int INVALID_POINTER = -1; 96 | private static final float DRAG_RATE = .5f; 97 | 98 | // Max amount of circle that can be filled by progress during swipe gesture, 99 | // where 1.0 is a full circle 100 | private static final float MAX_PROGRESS_ANGLE = .8f; 101 | 102 | private static final int SCALE_DOWN_DURATION = 150; 103 | 104 | private static final int ALPHA_ANIMATION_DURATION = 300; 105 | 106 | private static final int ANIMATE_TO_TRIGGER_DURATION = 200; 107 | 108 | private static final int ANIMATE_TO_START_DURATION = 200; 109 | 110 | // Default background for the progress spinner 111 | private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA; 112 | // Default offset in dips from the top of the view to where the progress spinner should stop 113 | private static final int DEFAULT_CIRCLE_TARGET = 64; 114 | 115 | /**底部view上拉最大的大小*/ 116 | private final int mBottomSpinnerOffsetEnd; 117 | 118 | private View mTarget; // the target of the gesture 119 | OnRefreshListener mListener; 120 | 121 | OnBottomRefreshListener mListenerBottom; 122 | 123 | boolean mRefreshing = false; 124 | 125 | //boolean mRefreshingBottom = false; 126 | 127 | boolean mRefreshingBottom = false; 128 | 129 | ScrollView s; 130 | 131 | private int mTouchSlop; 132 | /**初始化也是64dp,作为一个界定点,最终的刷新动画需要停在这个点上,超过这个点会减缓滑动速度*/ 133 | private float mTotalDragDistance = -1; 134 | 135 | // If nested scrolling is enabled, the total amount that needed to be 136 | // consumed by this as the nested scrolling parent is used in place of the 137 | // overscroll determined by MOVE events in the onTouch handler 138 | /**顶部没有被消费的*/ 139 | private float mTotalUnconsumed; 140 | /**底部滑动没有被消费的*/ 141 | private float mTotalUnconsumedBottom; 142 | private final NestedScrollingParentHelper mNestedScrollingParentHelper; 143 | private final NestedScrollingChildHelper mNestedScrollingChildHelper; 144 | private final int[] mParentScrollConsumed = new int[2]; 145 | private final int[] mParentOffsetInWindow = new int[2]; 146 | private boolean mNestedScrollInProgress; 147 | 148 | private int mMediumAnimationDuration; 149 | int mCurrentTargetOffsetTop; 150 | /**目前底部circle的底部*/ 151 | int mCurrentTargetOffsetBottom; 152 | 153 | private float mInitialMotionY; 154 | private float mInitialDownY; 155 | private boolean mIsBeingDragged; 156 | private int mActivePointerId = INVALID_POINTER; 157 | // Whether this item is scaled up rather than clipped 158 | /**如果在z轴它的上面没有东西,那么为true*/ 159 | boolean mScale; 160 | 161 | // Target is returning to its start offset because it was cancelled or a 162 | // refresh was triggered. 163 | private boolean mReturningToStart; 164 | /**是否正在返回底部*/ 165 | private boolean mReturningToEnd ; 166 | private final DecelerateInterpolator mDecelerateInterpolator; 167 | private static final int[] LAYOUT_ATTRS = new int[] { 168 | android.R.attr.enabled 169 | }; 170 | 171 | CircleImageView mCircleView; 172 | 173 | 174 | CircleImageView mCircleViewBottom; 175 | private int mCircleViewIndex = -1; 176 | /**bottomCircle在视图中的顺序*/ 177 | private int mCircleViewBottomIndex=-1; 178 | 179 | protected int mFrom; 180 | 181 | float mStartingScale; 182 | /**原始顶部*/ 183 | protected int mOriginalOffsetTop; 184 | /**原始底部*/ 185 | protected int mOriginalOffsetBottom; 186 | /**默认64dp*/ 187 | int mSpinnerOffsetEnd; 188 | /**底部spinner可以上拉到最大高度*/ 189 | int mSpinnerBottomOffsetEnd; 190 | MaterialProgressDrawable mProgress; 191 | /**底部Circle的drawable*/ 192 | MaterialProgressDrawable mProgressBottom; 193 | 194 | private Animation mScaleAnimation; 195 | 196 | private Animation mScaleDownAnimation; 197 | /**顶部circleView的透明度启动动画*/ 198 | private Animation mAlphaStartAnimation; 199 | /**底部 bottom view的透明度启动动画*/ 200 | private Animation mAlphaStartAnimationBottom; 201 | /**顶部circleView的透明度最大动画*/ 202 | private Animation mAlphaMaxAnimation; 203 | /**底部 bottom view的透明度最大动画*/ 204 | private Animation mAlphaMaxAnimationBottom; 205 | 206 | private Animation mScaleDownToStartAnimation; 207 | /**top circle标志位*/ 208 | boolean mNotify; 209 | 210 | /**bottom circle view 标志位*/ 211 | boolean mNotifyBottom; 212 | 213 | /**circle的初始上限*/ 214 | private int mCircleDiameter; 215 | 216 | // Whether the client has set a custom starting position; 217 | boolean mUsingCustomStart; 218 | 219 | private OnChildScrollUpCallback mChildScrollUpCallback; 220 | 221 | 222 | Handler hd =new Handler(){ 223 | @Override 224 | public void handleMessage(Message msg) { 225 | Log.d("fish","getMessageName="+msg.what); 226 | reset(); 227 | super.handleMessage(msg); 228 | } 229 | }; 230 | 231 | //add 232 | private boolean mBottomIsScrolling; 233 | /**第一次测量标志位*/ 234 | private boolean firstMeasure ; 235 | 236 | /**刷新动画监听,在动画结束的时候判断,如果还在刷新就继续显示circle,*/ 237 | private AnimationListener mRefreshListenerBottom = new AnimationListener() { 238 | @Override 239 | public void onAnimationStart(Animation animation) { 240 | } 241 | 242 | @Override 243 | public void onAnimationRepeat(Animation animation) { 244 | } 245 | 246 | @Override 247 | public void onAnimationEnd(Animation animation) { 248 | if (mRefreshingBottom) { 249 | // Make sure the progress view is fully visible 250 | mProgressBottom.setAlpha(MAX_ALPHA); 251 | mProgressBottom.start(); 252 | if (mNotifyBottom) { 253 | if (mListenerBottom != null) { 254 | mListenerBottom.onBottomRefresh(); 255 | Log.e("fish","mListenerBottom!= null"); 256 | }else{ 257 | Log.e("fish","mListenerBottom == null"); 258 | } 259 | } 260 | mCurrentTargetOffsetBottom = mCircleViewBottom.getBottom(); 261 | } else { 262 | reset(); 263 | } 264 | } 265 | }; 266 | 267 | /**刷新动画监听,在动画结束的时候判断,如果还在刷新就继续显示circle,*/ 268 | private AnimationListener mRefreshListener = new AnimationListener() { 269 | @Override 270 | public void onAnimationStart(Animation animation) { 271 | } 272 | 273 | @Override 274 | public void onAnimationRepeat(Animation animation) { 275 | } 276 | 277 | @Override 278 | public void onAnimationEnd(Animation animation) { 279 | if (mRefreshing) { 280 | mProgress.setAlpha(MAX_ALPHA); 281 | mProgress.start(); 282 | if (mNotify) { 283 | if (mListener != null) { 284 | mListener.onRefresh(); 285 | } 286 | } 287 | mCurrentTargetOffsetTop = mCircleView.getTop(); 288 | } else { 289 | reset(); 290 | } 291 | } 292 | }; 293 | 294 | 295 | 296 | /**重设状态,将circle隐藏起来*/ 297 | void reset() { 298 | mCircleView.clearAnimation(); 299 | 300 | mCircleViewBottom.clearAnimation(); 301 | 302 | mProgress.stop(); 303 | mCircleView.setVisibility(View.GONE); 304 | mProgressBottom.stop(); 305 | mCircleViewBottom.setVisibility(View.GONE); 306 | 307 | setColorViewAlpha(MAX_ALPHA); 308 | if (mScale) { 309 | setAnimationProgress(0 /* animation complete and view is hidden */); 310 | } else { 311 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop,//往回推 312 | true /* requires update */); 313 | setTargetOffsetTopAndBottomForBottom(mOriginalOffsetBottom - mCurrentTargetOffsetBottom,true); 314 | } 315 | mCurrentTargetOffsetTop = mCircleView.getTop(); 316 | mCurrentTargetOffsetBottom = mCircleViewBottom.getBottom(); 317 | } 318 | 319 | @Override 320 | public void setEnabled(boolean enabled) { 321 | super.setEnabled(enabled); 322 | if (!enabled) { 323 | reset(); 324 | } 325 | } 326 | 327 | @Override 328 | protected void onDetachedFromWindow() { 329 | super.onDetachedFromWindow(); 330 | reset(); 331 | } 332 | /**设置透明度,包括circleView和透明度*/ 333 | private void setColorViewAlpha(int targetAlpha) { 334 | mCircleView.getBackground().setAlpha(targetAlpha); 335 | mProgress.setAlpha(targetAlpha); 336 | 337 | 338 | mCircleViewBottom.getBackground().setAlpha(targetAlpha); 339 | mProgressBottom.setAlpha(targetAlpha); 340 | } 341 | 342 | /** 343 | * The refresh indicator starting and resting position is always positioned 344 | * near the top of the refreshing content. This position is a consistent 345 | * location, but can be adjusted in either direction based on whether or not 346 | * there is a toolbar or actionbar present. 347 | *

348 | * Note: Calling this will reset the position of the refresh indicator to 349 | * start. 350 | *

351 | * 352 | * @param scale Set to true if there is no view at a higher z-order than where the progress 353 | * spinner is set to appear. Setting it to true will cause indicator to be scaled 354 | * up rather than clipped. 355 | * @param start The offset in pixels from the top of this view at which the 356 | * progress spinner should appear. 357 | * @param end The offset in pixels from the top of this view at which the 358 | * progress spinner should come to rest after a successful swipe 359 | * gesture. 360 | *

这个方法就是自己设置circle的起始结束位置

361 | */ 362 | public void setProgressViewOffset(boolean scale, int start, int end) { 363 | mScale = scale; 364 | mOriginalOffsetTop = start; 365 | mSpinnerOffsetEnd = end; 366 | mUsingCustomStart = true; 367 | reset(); 368 | mRefreshing = false; 369 | } 370 | 371 | /** 372 | * @return The offset in pixels from the top of this view at which the progress spinner should 373 | * appear. 374 | */ 375 | public int getProgressViewStartOffset() { 376 | return mOriginalOffsetTop; 377 | } 378 | 379 | /** 380 | * @return The offset in pixels from the top of this view at which the progress spinner should 381 | * come to rest after a successful swipe gesture. 382 | */ 383 | public int getProgressViewEndOffset() { 384 | return mSpinnerOffsetEnd; 385 | } 386 | 387 | /** 388 | * The refresh indicator resting position is always positioned near the top 389 | * of the refreshing content. This position is a consistent location, but 390 | * can be adjusted in either direction based on whether or not there is a 391 | * toolbar or actionbar present. 392 | * 393 | * @param scale Set to true if there is no view at a higher z-order than where the progress 394 | * spinner is set to appear. Setting it to true will cause indicator to be scaled 395 | * up rather than clipped. 396 | * @param end The offset in pixels from the top of this view at which the 397 | * progress spinner should come to rest after a successful swipe 398 | * gesture. 399 | */ 400 | public void setProgressViewEndTarget(boolean scale, int end) { 401 | mSpinnerOffsetEnd = end; 402 | mScale = scale; 403 | mCircleView.invalidate(); 404 | mCircleViewBottom.invalidate(); 405 | } 406 | 407 | /** 408 | * One of DEFAULT, or LARGE. 409 | * 设置progressBar的size大小,他是固定的,只有大和一般两种大小 410 | */ 411 | public void setSize(int size) { 412 | if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) { 413 | return; 414 | } 415 | final DisplayMetrics metrics = getResources().getDisplayMetrics(); 416 | if (size == MaterialProgressDrawable.LARGE) { 417 | mCircleDiameter = (int) (CIRCLE_DIAMETER_LARGE * metrics.density); 418 | } else { 419 | mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density); 420 | } 421 | // force the bounds of the progress circle inside the circle view to 422 | // update by setting it to null before updating its size and then 423 | // re-setting it 424 | mCircleView.setImageDrawable(null); 425 | mCircleViewBottom.setImageDrawable(null); 426 | mProgress.updateSizes(size); 427 | mCircleView.setImageDrawable(mProgress); 428 | mProgressBottom.updateSizes(size); 429 | // mProgressBottom.showArrow(true); 430 | // mProgressBottom.setRotation(0.5f); 431 | mCircleViewBottom.setImageDrawable(mProgressBottom); 432 | 433 | } 434 | 435 | /** 436 | * Simple constructor to use when creating a GZoomSwifrefresh from code. 437 | * 438 | * @param context 439 | */ 440 | public GZoomSwifrefresh(Context context) { 441 | this(context, null); 442 | } 443 | 444 | /** 445 | * Constructor that is called when inflating GZoomSwifrefresh from XML. 446 | * 447 | * @param context 448 | * @param attrs 449 | */ 450 | public GZoomSwifrefresh(Context context, AttributeSet attrs) { 451 | super(context, attrs); 452 | 453 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 454 | 455 | mMediumAnimationDuration = getResources().getInteger( 456 | android.R.integer.config_mediumAnimTime); 457 | /**因为要绘制小圈圈,所以是需要绘制的*/ 458 | setWillNotDraw(false); 459 | mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR); 460 | 461 | final DisplayMetrics metrics = getResources().getDisplayMetrics(); 462 | mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density); 463 | 464 | createProgressView(); 465 | //按照顺序绘制 466 | ViewCompat.setChildrenDrawingOrderEnabled(this, true); 467 | // the absolute offset has to take into account that the circle starts at an offset 468 | mSpinnerOffsetEnd = (int) (DEFAULT_CIRCLE_TARGET * metrics.density); 469 | //这是错的,只是先初始化 470 | mBottomSpinnerOffsetEnd = mSpinnerOffsetEnd; 471 | 472 | mTotalDragDistance = mSpinnerOffsetEnd; 473 | mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); 474 | 475 | mNestedScrollingChildHelper = new NestedScrollingChildHelper(this); 476 | setNestedScrollingEnabled(true); 477 | 478 | mOriginalOffsetTop = mCurrentTargetOffsetTop = -mCircleDiameter; 479 | moveToStart(1.0f); 480 | //这个也是错的,只是先初始化一下 481 | mOriginalOffsetBottom = mCircleDiameter; 482 | mCurrentTargetOffsetBottom = mCircleDiameter; 483 | 484 | 485 | final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 486 | setEnabled(a.getBoolean(0, true)); 487 | a.recycle(); 488 | 489 | firstMeasure = true; 490 | WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); 491 | int windowHeight = wm.getDefaultDisplay().getHeight(); 492 | Log.e("fish","init+-+windowHeight=="+windowHeight); 493 | mOriginalOffsetBottom = windowHeight - mCircleDiameter/2; 494 | Log.e("fish","init+-+mOriginalOffsetBottom=="+mOriginalOffsetBottom); 495 | 496 | mCurrentTargetOffsetBottom = mOriginalOffsetBottom; 497 | mSpinnerBottomOffsetEnd = mOriginalOffsetBottom - mSpinnerOffsetEnd; 498 | 499 | 500 | } 501 | @Override 502 | protected int getChildDrawingOrder(int childCount, int i) { 503 | if (mCircleViewIndex < 0) { 504 | return i; 505 | } else if (i == childCount - 1) { 506 | // Draw the selected child last 507 | return mCircleViewIndex; 508 | } else if (i >= mCircleViewIndex) { 509 | // Move the children after the selected child earlier one 510 | return i + 1; 511 | } else { 512 | // Keep the children before the selected child the same 513 | return i; 514 | } 515 | //return super.getChildDrawingOrder(childCount,i); 516 | } 517 | /**创造ProgressView,给他们设置ImageDrawable*/ 518 | private void createProgressView() { 519 | mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT); 520 | 521 | mCircleViewBottom = new CircleImageView(getContext(),CIRCLE_BG_LIGHT); 522 | 523 | mProgress = new MaterialProgressDrawable(getContext(), this); 524 | mProgress.setBackgroundColor(CIRCLE_BG_LIGHT); 525 | mCircleView.setImageDrawable(mProgress); 526 | mCircleView.setVisibility(View.GONE); 527 | addView(mCircleView); 528 | 529 | mProgressBottom = new MaterialProgressDrawable(getContext(),this); 530 | mProgressBottom.setBackgroundColor(CIRCLE_BG_LIGHT); 531 | mCircleViewBottom.setImageDrawable(mProgressBottom); 532 | // mProgressBottom.showArrow(true); 533 | // mProgressBottom.setRotation(0.5f); 534 | // mProgressBottom.start(); 535 | mCircleViewBottom.setVisibility(View.GONE); 536 | addView(mCircleViewBottom); 537 | //addView(mCircleView,2); 538 | } 539 | 540 | /** 541 | * Set the listener to be notified when a refresh is triggered via the swipe 542 | * gesture. 543 | */ 544 | public void setOnRefreshListener(OnRefreshListener listener) { 545 | mListener = listener; 546 | } 547 | 548 | 549 | public void setOnBottomRefreshListenrer(OnBottomRefreshListener listener) 550 | { 551 | mListenerBottom = listener; 552 | } 553 | 554 | 555 | /** 556 | * Pre API 11, alpha is used to make the progress circle appear instead of scale. 557 | * 这里是判断机型版本有没有大于安卓3.0 558 | * 小于返回true 559 | */ 560 | private boolean isAlphaUsedForScale() { 561 | return android.os.Build.VERSION.SDK_INT < 11;//11 是Android3.0 562 | } 563 | 564 | /** 565 | * Notify the widget that refresh state has changed. Do not call this when 566 | * refresh is triggered by a swipe gesture. 567 | * 这个方法是暴露给外部手动停止滑动的 568 | * @param refreshing Whether or not the view should show refresh progress. 569 | */ 570 | public void setRefreshing(boolean refreshing) { 571 | if (refreshing && mRefreshing != refreshing) { 572 | // scale and show 573 | mRefreshing = refreshing; 574 | int endTarget = 0; 575 | if (!mUsingCustomStart) { 576 | endTarget = mSpinnerOffsetEnd + mOriginalOffsetTop; 577 | } else { 578 | endTarget = mSpinnerOffsetEnd; 579 | } 580 | setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop, 581 | true /* requires update */); 582 | mNotify = false; 583 | mNotifyBottom = false; 584 | startScaleUpAnimation(mRefreshListener); 585 | } else { 586 | setRefreshing(refreshing, false /* notify */); 587 | } 588 | } 589 | 590 | public void setBottomRefreshing(boolean refreshing) { 591 | if (refreshing && mRefreshingBottom != refreshing) { 592 | // scale and show 593 | mRefreshingBottom = refreshing; 594 | int endTarget = 0; 595 | if (!mUsingCustomStart) { 596 | endTarget = (int) (mOriginalOffsetBottom - mSpinnerOffsetEnd*1.5); 597 | } else { 598 | endTarget = mSpinnerOffsetEnd; 599 | } 600 | setTargetOffsetTopAndBottomForBottom(endTarget - mCurrentTargetOffsetBottom, 601 | true /* requires update */); 602 | mNotifyBottom = false; 603 | // mNotifyBottom = false; 604 | startScaleUpAnimationBottom(mRefreshListenerBottom); 605 | } else { 606 | setRefreshingBottom(refreshing, false /* notify */); 607 | } 608 | } 609 | 610 | 611 | /**开始顶部 CircleView的动画*/ 612 | private void startScaleUpAnimation(AnimationListener listener) { 613 | mCircleView.setVisibility(View.VISIBLE); 614 | if (android.os.Build.VERSION.SDK_INT >= 11) { 615 | // Pre API 11, alpha is used in place of scale up to show the 616 | // progress circle appearing. 617 | // Don't adjust the alpha during appearance otherwise. 618 | mProgress.setAlpha(MAX_ALPHA); 619 | } 620 | mScaleAnimation = new Animation() { 621 | @Override 622 | public void applyTransformation(float interpolatedTime, Transformation t) { 623 | setAnimationProgress(interpolatedTime);//interpolatedTime 从小到大,0.0-1 624 | } 625 | }; 626 | mScaleAnimation.setDuration(mMediumAnimationDuration); 627 | if (listener != null) { 628 | mCircleView.setAnimationListener(listener); 629 | } 630 | mCircleView.clearAnimation(); 631 | mCircleView.startAnimation(mScaleAnimation); 632 | } 633 | 634 | /**开始底部 CircleView的动画*/ 635 | private void startScaleUpAnimationBottom(AnimationListener listener) { 636 | mCircleViewBottom.setVisibility(View.VISIBLE); 637 | if (android.os.Build.VERSION.SDK_INT >= 11) { 638 | // Pre API 11, alpha is used in place of scale up to show the 639 | // progress circle appearing. 640 | // Don't adjust the alpha during appearance otherwise. 641 | mProgressBottom.setAlpha(MAX_ALPHA); 642 | } 643 | mScaleAnimation = new Animation() { 644 | @Override 645 | public void applyTransformation(float interpolatedTime, Transformation t) { 646 | setAnimationProgressBottom(interpolatedTime);//interpolatedTime 从小到大,0.0-1 647 | } 648 | }; 649 | mScaleAnimation.setDuration(mMediumAnimationDuration); 650 | if (listener != null) { 651 | mCircleViewBottom.setAnimationListener(listener); 652 | } 653 | mCircleViewBottom.clearAnimation(); 654 | mCircleViewBottom.startAnimation(mScaleAnimation); 655 | } 656 | 657 | 658 | /** 659 | * Pre API 11, this does an alpha animation. 660 | * @param progress 661 | * 设置CircleView的进程 662 | */ 663 | void setAnimationProgress(float progress) { 664 | if (isAlphaUsedForScale()) { 665 | setColorViewAlpha((int) (progress * MAX_ALPHA)); 666 | Log.e(LOG_TAG,"setAnimationProgress,setColorViewAlpha"); 667 | } else { 668 | //不出所料,我走的是这个方法 669 | ViewCompat.setScaleX(mCircleView, progress); 670 | ViewCompat.setScaleY(mCircleView, progress); 671 | Log.e(LOG_TAG,"setAnimationProgress,ViewCompat.setScale,progress=="+progress); 672 | } 673 | } 674 | 675 | 676 | void setAnimationProgressBottom(float progress) { 677 | if (isAlphaUsedForScale()) { 678 | setColorViewAlpha((int) (progress * MAX_ALPHA)); 679 | Log.e(LOG_TAG,"setAnimationProgress,setColorViewAlpha"); 680 | } else { 681 | //不出所料,我走的是这个方法 682 | ViewCompat.setScaleX(mCircleViewBottom, progress); 683 | ViewCompat.setScaleY(mCircleViewBottom, progress); 684 | Log.e(LOG_TAG,"setAnimationProgressBottom,ViewCompat.setScale,progress=="+progress); 685 | } 686 | } 687 | 688 | /**设置refreshing的状态,如果不相应就相应的改变,应该是为了设置刷不刷新*/ 689 | private void setRefreshing(boolean refreshing, final boolean notify) { 690 | if (mRefreshing != refreshing) { 691 | mNotify = notify; 692 | ensureTarget(); 693 | mRefreshing = refreshing; 694 | if (mRefreshing) {//如果标志位正确,就启动刷新动画,将circle一步步移到中间那个位置 695 | animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener); 696 | } else { 697 | startScaleDownAnimation(mRefreshListener); 698 | } 699 | } 700 | } 701 | 702 | 703 | 704 | /**设置refreshing的状态,如果不相应就相应的改变,应该是为了设置刷不刷新*/ 705 | private void setRefreshingBottom(boolean refreshing, final boolean notify) { 706 | if (mRefreshingBottom != refreshing) { 707 | mNotifyBottom = notify; 708 | ensureTarget(); 709 | mRefreshingBottom = refreshing; 710 | if (mRefreshingBottom) {//如果标志位正确,就启动刷新动画,将circle一步步移到中间那个位置 711 | animateOffsetToCorrectPositionBottom(mCurrentTargetOffsetBottom, mRefreshListenerBottom);// 712 | //add 713 | //reset(); 714 | //不行,加上去之后转都不转 715 | } else { 716 | startScaleDownAnimationBottom(mRefreshListenerBottom); 717 | } 718 | } 719 | } 720 | 721 | 722 | 723 | 724 | /**开始mScaleDownAnimation动画,还有对于circle进程的改变 725 | * 就是在你手指拖动的过程中改变 726 | * 727 | *

说白了就是对circleBottom进行倒着转动画

728 | * */ 729 | void startScaleDownAnimation(AnimationListener listener) { 730 | mScaleDownAnimation = new Animation() { 731 | @Override 732 | public void applyTransformation(float interpolatedTime, Transformation t) { 733 | setAnimationProgress(1 - interpolatedTime);//倒着转? 734 | } 735 | }; 736 | mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION); 737 | mCircleView.setAnimationListener(listener); 738 | mCircleView.clearAnimation(); 739 | mCircleView.startAnimation(mScaleDownAnimation); 740 | } 741 | 742 | /**开始mScaleDownAnimation动画,还有对于circle进程的改变 743 | * 就是在你手指拖动的过程中改变 744 | * 745 | * bottomn 746 | * */ 747 | void startScaleDownAnimationBottom(AnimationListener listener) { 748 | mScaleDownAnimation = new Animation() { 749 | @Override 750 | public void applyTransformation(float interpolatedTime, Transformation t) { 751 | setAnimationProgressBottom(1 - interpolatedTime);//倒着转? 752 | } 753 | }; 754 | mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION); 755 | mCircleViewBottom.setAnimationListener(listener); 756 | mCircleViewBottom.clearAnimation(); 757 | mCircleViewBottom.startAnimation(mScaleDownAnimation); 758 | } 759 | 760 | /**开始透明度的变化,最终76 761 | *

for top CircleView

762 | * */ 763 | private void startProgressAlphaStartAnimation() { 764 | mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA); 765 | } 766 | 767 | /**开始透明度的变化,最终76 768 | *

for bottom CircleView

769 | * */ 770 | private void startProgressAlphaStartAnimationBottom() { 771 | mAlphaStartAnimationBottom = startAlphaAnimationBottom(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA); 772 | } 773 | 774 | 775 | /**开启到最大的透明度,255 776 | *

for top CircleView

*/ 777 | private void startProgressAlphaMaxAnimation() { 778 | mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA); 779 | } 780 | 781 | /**开启到最大的透明度,255 782 | *

for top CircleView

*/ 783 | private void startProgressAlphaMaxAnimationBottom() { 784 | mAlphaMaxAnimationBottom = startAlphaAnimationBottom(mProgressBottom.getAlpha(), MAX_ALPHA); 785 | } 786 | 787 | 788 | /**透明度动画。两个参数分别是开始透明度和结束,这样可能效果不会太突兀? 789 | *

for top circle view

790 | * */ 791 | private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) { 792 | // Pre API 11, alpha is used in place of scale. Don't also use it to 793 | // show the trigger point. 794 | if (mScale && isAlphaUsedForScale()) { 795 | return null; 796 | } 797 | Animation alpha = new Animation() { 798 | @Override 799 | public void applyTransformation(float interpolatedTime, Transformation t) { 800 | mProgress.setAlpha( 801 | (int) (startingAlpha + ((endingAlpha - startingAlpha) * interpolatedTime))); 802 | } 803 | }; 804 | alpha.setDuration(ALPHA_ANIMATION_DURATION); 805 | // Clear out the previous animation listeners. 806 | mCircleView.setAnimationListener(null); 807 | mCircleView.clearAnimation(); 808 | mCircleView.startAnimation(alpha); 809 | return alpha; 810 | } 811 | 812 | 813 | /**透明度动画。两个参数分别是开始透明度和结束,这样可能效果不会太突兀? 814 | *

for top circle view

815 | * */ 816 | private Animation startAlphaAnimationBottom(final int startingAlpha, final int endingAlpha) { 817 | // Pre API 11, alpha is used in place of scale. Don't also use it to 818 | // show the trigger point. 819 | if (mScale && isAlphaUsedForScale()) { 820 | return null; 821 | } 822 | Animation alpha = new Animation() { 823 | @Override 824 | public void applyTransformation(float interpolatedTime, Transformation t) { 825 | mProgress.setAlpha( 826 | (int) (startingAlpha + ((endingAlpha - startingAlpha) * interpolatedTime))); 827 | } 828 | }; 829 | alpha.setDuration(ALPHA_ANIMATION_DURATION); 830 | // Clear out the previous animation listeners. 831 | mCircleViewBottom.setAnimationListener(null); 832 | mCircleViewBottom.clearAnimation(); 833 | mCircleViewBottom.startAnimation(alpha); 834 | return alpha; 835 | } 836 | 837 | 838 | 839 | /** 840 | * @deprecated Use {@link #setProgressBackgroundColorSchemeResource(int)} 841 | */ 842 | @Deprecated 843 | public void setProgressBackgroundColor(int colorRes) { 844 | setProgressBackgroundColorSchemeResource(colorRes); 845 | } 846 | 847 | /** 848 | * Set the background color of the progress spinner disc. 849 | * 850 | * @param colorRes Resource id of the color. 851 | */ 852 | public void setProgressBackgroundColorSchemeResource(@ColorRes int colorRes) { 853 | setProgressBackgroundColorSchemeColor(ContextCompat.getColor(getContext(), colorRes)); 854 | } 855 | 856 | /** 857 | * Set the background color of the progress spinner disc. 858 | * 859 | * @param color 860 | */ 861 | public void setProgressBackgroundColorSchemeColor(@ColorInt int color) { 862 | mCircleView.setBackgroundColor(color); 863 | mProgress.setBackgroundColor(color); 864 | } 865 | 866 | /** 867 | * @deprecated Use {@link #setColorSchemeResources(int...)} 868 | */ 869 | @Deprecated 870 | public void setColorScheme(@ColorInt int... colors) { 871 | setColorSchemeResources(colors); 872 | } 873 | 874 | /** 875 | * Set the color resources used in the progress animation from color resources. 876 | * The first color will also be the color of the bar that grows in response 877 | * to a user swipe gesture. 878 | * 879 | * @param colorResIds 880 | */ 881 | public void setColorSchemeResources( int... colorResIds) {//这里被我删了注解 882 | final Context context = getContext(); 883 | int[] colorRes = new int[colorResIds.length]; 884 | for (int i = 0; i < colorResIds.length; i++) { 885 | colorRes[i] = ContextCompat.getColor(context, colorResIds[i]); 886 | } 887 | setColorSchemeColors(colorRes); 888 | } 889 | 890 | /** 891 | * Set the colors used in the progress animation. The first 892 | * color will also be the color of the bar that grows in response to a user 893 | * swipe gesture. 894 | * 895 | * @param colors 896 | */ 897 | public void setColorSchemeColors(@ColorInt int... colors) { 898 | ensureTarget(); 899 | mProgress.setColorSchemeColors(colors); 900 | } 901 | /**设置底部circle加载颜色*/ 902 | public void setBottomColorSchemeColors(int ...colors) 903 | { 904 | ensureTarget(); 905 | mProgressBottom.setColorSchemeColors(colors); 906 | } 907 | 908 | 909 | /** 910 | * @return Whether the SwipeRefreshWidget is actively showing refresh 911 | * progress. 912 | */ 913 | public boolean isRefreshing() { 914 | return mRefreshing; 915 | } 916 | 917 | /**这个就是要找到滑动的目标,也就是mTarget 918 | * ,他就是找到在子view中接着上面刷新圆环的第一个,也就是我们放在格局中的第一个 919 | * */ 920 | private void ensureTarget() { 921 | // Don't bother getting the parent height if the parent hasn't been laid 922 | // out yet. 923 | if (mTarget == null) {//手势目标 924 | for (int i = 0; i < getChildCount(); i++) { 925 | View child = getChildAt(i); 926 | if (!child.equals(mCircleView)&&!child.equals(mCircleViewBottom)) { 927 | mTarget = child; 928 | Log.e(LOG_TAG,"ensureTarget,mTarget=="+mTarget.toString()); 929 | break; 930 | } 931 | } 932 | } 933 | } 934 | 935 | /** 936 | * Set the distance to trigger a sync in dips 937 | *设置拖动的极限距离 938 | * @param distance 939 | */ 940 | public void setDistanceToTriggerSync(int distance) { 941 | mTotalDragDistance = distance; 942 | } 943 | 944 | @Override 945 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 946 | final int width = getMeasuredWidth(); 947 | final int height = getMeasuredHeight(); 948 | if (getChildCount() == 0) { 949 | return; 950 | } 951 | if (mTarget == null) { 952 | ensureTarget(); 953 | } 954 | if (mTarget == null) { 955 | return; 956 | } 957 | final View child = mTarget; 958 | final int childLeft = getPaddingLeft(); 959 | final int childTop = getPaddingTop(); 960 | final int childWidth = width - getPaddingLeft() - getPaddingRight(); 961 | final int childHeight = height - getPaddingTop() - getPaddingBottom(); 962 | child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); 963 | Log.e(LOG_TAG,"onLayout,childTop=="+childTop+",childHeight"+childHeight); 964 | //草,就是因为这个,才会滚不回去,在重绘的过程中会进行新一次的onLayout 965 | /* if(firstMeasure) {//其实已经没有必要摆在这里了 966 | // mOriginalOffsetBottom = height + mCircleDiameter; 967 | //mOriginalOffsetBottom = 1250; 968 | mCurrentTargetOffsetBottom = mOriginalOffsetBottom; 969 | mSpinnerBottomOffsetEnd = mOriginalOffsetBottom - mSpinnerOffsetEnd; 970 | firstMeasure = false; 971 | Log.e("fish", "firstMeasure,mCurrentTargetOffsetBottom==" + mCurrentTargetOffsetBottom + ",mSpinnerBottomOffsetEnd==" + mSpinnerBottomOffsetEnd); 972 | Log.e("fish", "firstMeasure,mOriginalOffsetBottom==" + mOriginalOffsetBottom); 973 | }*/ 974 | 975 | int circleWidth = mCircleView.getMeasuredWidth(); 976 | int circleHeight = mCircleView.getMeasuredHeight(); 977 | mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop, 978 | (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight); 979 | // mCircleView.layout((width / 2 - circleWidth / 2), childHeight/2, 980 | // (width / 2 + circleWidth / 2), childHeight/2 + circleHeight); 981 | 982 | // mCurrentTargetOffsetBottom = mOriginalOffsetBottom; 983 | int circleBottomWidth = mCircleViewBottom.getMeasuredWidth(); 984 | int circleBottomHeight = mCircleViewBottom.getMeasuredHeight(); 985 | mCircleViewBottom.setVisibility(View.VISIBLE); 986 | mCircleViewBottom.layout((width / 2 - circleBottomWidth / 2), mCurrentTargetOffsetBottom - circleBottomHeight, 987 | (width / 2 + circleBottomWidth / 2), mCurrentTargetOffsetBottom); 988 | // mProgressBottom.start(); 989 | } 990 | 991 | @Override 992 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 993 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 994 | if (mTarget == null) { 995 | ensureTarget(); 996 | } 997 | if (mTarget == null) { 998 | return; 999 | } 1000 | mTarget.measure(MeasureSpec.makeMeasureSpec( 1001 | getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 1002 | MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( 1003 | getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)); 1004 | mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY), 1005 | MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY)); 1006 | mCircleViewIndex = -1; 1007 | // Get the index of the circleview. 1008 | for (int index = 0; index < getChildCount(); index++) { 1009 | if (getChildAt(index) == mCircleView) { 1010 | mCircleViewIndex = index; 1011 | Log.e(LOG_TAG,"mCircleViewIndex=="+mCircleViewIndex); 1012 | break; 1013 | } 1014 | } 1015 | 1016 | mCircleViewBottom.measure(MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY), 1017 | MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY)); 1018 | mCircleViewBottomIndex = -1; 1019 | // Get the index of the circleview. 1020 | for (int index = 0; index < getChildCount(); index++) { 1021 | if (getChildAt(index) == mCircleViewBottom) { 1022 | mCircleViewBottomIndex = index; 1023 | Log.e(LOG_TAG,"mCircleViewBottomIndex=="+mCircleViewBottomIndex); 1024 | break; 1025 | } 1026 | } 1027 | 1028 | 1029 | } 1030 | 1031 | /** 1032 | * Get the diameter of the progress circle that is displayed as part of the 1033 | * swipe to refresh layout. 1034 | * 1035 | * @return Diameter in pixels of the progress circle view. 1036 | *

获取圆环直径

1037 | */ 1038 | public int getProgressCircleDiameter() { 1039 | return mCircleDiameter; 1040 | } 1041 | 1042 | /** 1043 | * @return Whether it is possible for the child view of this layout to 1044 | * scroll up. Override this if the child view is a custom view. 1045 | */ 1046 | public boolean canChildScrollUp() { 1047 | if (mChildScrollUpCallback != null) { 1048 | Log.e("fish","canChildScrollUp;mChildScrollUpCallback != null"); 1049 | return mChildScrollUpCallback.canChildScrollUp(this, mTarget); 1050 | 1051 | } 1052 | if (android.os.Build.VERSION.SDK_INT < 14) { 1053 | Log.e("fish","canChildScrollUp;Build.VERSION.SDK_INT < 14"); 1054 | if (mTarget instanceof AbsListView) { 1055 | Log.e("fish","canChildScrollUp;mTarget instanceof AbsListView"); 1056 | final AbsListView absListView = (AbsListView) mTarget; 1057 | return absListView.getChildCount() > 0 1058 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 1059 | .getTop() < absListView.getPaddingTop()); 1060 | } else { 1061 | Log.e("fish","canChildScrollUp;mTarget !instanceof AbsListView"); 1062 | return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0; 1063 | } 1064 | } else { 1065 | // Log.e("fish","canChildScrollUp;android.os.Build.VERSION.SDK_INT > 14"); 1066 | //这个可以用,判断是否可以向下拉 1067 | // boolean re = ViewCompat.canScrollVertically(mTarget,1); 1068 | //Log.e("fish","canChildScrollDown?"+re); 1069 | return ViewCompat.canScrollVertically(mTarget, -1); 1070 | } 1071 | } 1072 | 1073 | 1074 | //自己写的方法 1075 | /**判断是否而已向下拉*/ 1076 | public boolean canChildScrollDown() 1077 | { 1078 | return ViewCompat.canScrollVertically(mTarget, 1); 1079 | } 1080 | 1081 | 1082 | /** 1083 | * Set a callback to override {@link GZoomSwifrefresh#canChildScrollUp()} method. Non-null 1084 | * callback will return the value provided by the callback and ignore all internal logic. 1085 | * @param callback Callback that should be called when canChildScrollUp() is called. 1086 | */ 1087 | public void setOnChildScrollUpCallback(@Nullable OnChildScrollUpCallback callback) { 1088 | mChildScrollUpCallback = callback; 1089 | } 1090 | 1091 | @Override 1092 | public boolean onInterceptTouchEvent(MotionEvent ev) { 1093 | //先确定滑动的目标对象 1094 | ensureTarget(); 1095 | 1096 | final int action = MotionEventCompat.getActionMasked(ev); 1097 | int pointerIndex; 1098 | //点击事件开始,将标志位清空 1099 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { 1100 | mReturningToStart = false; 1101 | } 1102 | /**isEnabled 可用 1103 | * 1104 | * canChildScrollUp 子view还可以向上拉 1105 | * */ 1106 | if (!isEnabled() || mReturningToStart || canChildScrollUp() 1107 | || mRefreshing || mNestedScrollInProgress) { 1108 | // Fail fast if we're not in a state where a swipe is possible 1109 | return false; 1110 | } 1111 | 1112 | switch (action) { 1113 | case MotionEvent.ACTION_DOWN: 1114 | Log.e("fish","onInterceptTouchEvent,ACTION_DOWN"); 1115 | setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true); 1116 | mActivePointerId = ev.getPointerId(0); 1117 | mIsBeingDragged = false; 1118 | 1119 | pointerIndex = ev.findPointerIndex(mActivePointerId); 1120 | if (pointerIndex < 0) { 1121 | return false; 1122 | } 1123 | mInitialDownY = ev.getY(pointerIndex); 1124 | break; 1125 | 1126 | case MotionEvent.ACTION_MOVE://这个move基本不触发 1127 | Log.e("fish","onInterceptTouchEvent,ACTION_MOVE"); 1128 | if (mActivePointerId == INVALID_POINTER) { 1129 | Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id."); 1130 | return false; 1131 | } 1132 | 1133 | pointerIndex = ev.findPointerIndex(mActivePointerId); 1134 | if (pointerIndex < 0) { 1135 | return false; 1136 | } 1137 | final float y = ev.getY(pointerIndex); 1138 | startDragging(y); 1139 | break; 1140 | 1141 | case MotionEventCompat.ACTION_POINTER_UP: 1142 | Log.e("fish","onInterceptTouchEvent,MotionEventCompat.ACTION_POINTER_UP"); 1143 | onSecondaryPointerUp(ev); 1144 | break; 1145 | 1146 | case MotionEvent.ACTION_UP: 1147 | Log.e("fish","onInterceptTouchEvent,motionEvent.ACTION_UP"); 1148 | case MotionEvent.ACTION_CANCEL: 1149 | Log.e("fish","onInterceptTouchEvent,MotionEvent.ACTION_CANCEL"); 1150 | mIsBeingDragged = false; 1151 | mActivePointerId = INVALID_POINTER; 1152 | break; 1153 | } 1154 | 1155 | return mIsBeingDragged; 1156 | } 1157 | 1158 | @Override 1159 | public void requestDisallowInterceptTouchEvent(boolean b) { 1160 | // if this is a List < L or another view that doesn't support nested 1161 | // scrolling, ignore this request so that the vertical scroll event 1162 | // isn't stolen 1163 | if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView) 1164 | || (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) { 1165 | // Nope. 1166 | } else { 1167 | super.requestDisallowInterceptTouchEvent(b); 1168 | } 1169 | } 1170 | 1171 | // NestedScrollingParent 1172 | 1173 | @Override 1174 | public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { 1175 | //target 发起滑动的字view,可以不是当前view的直接子view 1176 | //child 包含target的直接子view 1177 | //返回true表示要与target配套滑动,为true则下面的accepted也会被调用 1178 | //mReturningToStart是为了配合onTouchEvent的,这里我们不扩展 1179 | return isEnabled() && !mReturningToStart && !mRefreshing && !mRefreshingBottom //没有在刷新和返回途中 1180 | && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;//竖直方向 1181 | } 1182 | 1183 | @Override 1184 | public void onNestedScrollAccepted(View child, View target, int axes) { 1185 | Log.e(LOG_TAG,"onNestedScrollAccepted,axes="+axes); 1186 | // Reset the counter of how much leftover scroll needs to be consumed. 1187 | mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes); 1188 | // Dispatch up to the nested parent 1189 | startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);//调用自己child接口的方法,实现向上传递 1190 | mTotalUnconsumed = 0; 1191 | // 1192 | mTotalUnconsumedBottom = 0; 1193 | mNestedScrollInProgress = true; 1194 | 1195 | } 1196 | 1197 | /** 1198 | * 在child开始滑动之前会通知parent的这个方法,看看能不能滑动dx dy这么多 1199 | *这里是回调,parent可以先于child进行滑动 1200 | * */ 1201 | @Override 1202 | public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { 1203 | // If we are in the middle of consuming, a scroll, then we want to move the spinner back up 1204 | // before allowing the list to scroll 1205 | Log.e(LOG_TAG,"onNestedPreScroll,dy="+dy+",consume[1]=="+consumed[1]); 1206 | if (dy > 0 && mTotalUnconsumed > 0 && !mRefreshingBottom) {//向下拖dy小于0,所以这是为了处理拖circle到一半然后又缩回去的情况 1207 | if (dy > mTotalUnconsumed) {//拖动的很多,大于未消费的 1208 | consumed[1] = dy - (int) mTotalUnconsumed; 1209 | mTotalUnconsumed = 0; 1210 | } else {//拖动一点,我们全部用给自己 1211 | mTotalUnconsumed -= dy; 1212 | consumed[1] = dy; 1213 | } 1214 | moveSpinner(mTotalUnconsumed);//move 到这个位置 1215 | } 1216 | 1217 | //处理底部的,圆圈已经出来了之后它又向下拖 1218 | if(dy<0 && mTotalUnconsumedBottom > 0 && !mRefreshing) 1219 | { 1220 | Log.e("fish","dy<0 && mTotalUnconsumedBottom > 0+++dy=="+dy+",mTotalUnconsumedBottom=="+mTotalUnconsumedBottom); 1221 | if(-dy>mTotalUnconsumedBottom)//如果拖动的很多,就先给圆圈,然后还给子控件 1222 | { 1223 | consumed[1]= -(int) mTotalUnconsumedBottom; 1224 | mTotalUnconsumedBottom = 0; 1225 | mBottomIsScrolling = false; 1226 | }else{//否则,先给父控件 1227 | mTotalUnconsumedBottom +=dy; 1228 | consumed[1]=dy;//原来传回去的是正数,结果越滑越快。。。改成负数之后就对了,开心 1229 | } 1230 | moveBottomSpinner(mTotalUnconsumedBottom); 1231 | } 1232 | 1233 | 1234 | // If a client layout is using a custom start position自定义开始位置 for the circle 1235 | // view, they mean to hide it again before scrolling the child view 1236 | // If we get back to mTotalUnconsumed == 0 and there is more to go, hide 1237 | // the circle so it isn't exposed if its blocking content is moved 1238 | if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0 1239 | && Math.abs(dy - consumed[1]) > 0) { 1240 | mCircleView.setVisibility(View.GONE); 1241 | } 1242 | 1243 | // Now let our nested parent consume the leftovers 1244 | /**计算它的父层的消耗,加上去*/ 1245 | final int[] parentConsumed = mParentScrollConsumed; 1246 | if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) {//父控件处理了才会返回true 1247 | consumed[0] += parentConsumed[0]; 1248 | consumed[1] += parentConsumed[1]; 1249 | } 1250 | } 1251 | 1252 | @Override 1253 | public int getNestedScrollAxes() { 1254 | return mNestedScrollingParentHelper.getNestedScrollAxes(); 1255 | } 1256 | 1257 | @Override 1258 | public void onStopNestedScroll(View target) { 1259 | mNestedScrollingParentHelper.onStopNestedScroll(target); 1260 | mNestedScrollInProgress = false; 1261 | Log.e(LOG_TAG,"onStopNestedScroll,mTotalUnconsumed="+mTotalUnconsumed); 1262 | // Finish the spinner for nested scrolling if we ever consumed any 1263 | // unconsumed nested scroll 1264 | if (mTotalUnconsumed > 0 && !mRefreshingBottom) { 1265 | finishSpinner(mTotalUnconsumed); 1266 | mTotalUnconsumed = 0; 1267 | } 1268 | if(mTotalUnconsumedBottom > 0 && !mRefreshing) 1269 | { 1270 | Log.e("fish","onStopNestedScroll,mTotalUnconsumedBottom > 0"); 1271 | finishSpinnerBottom(mTotalUnconsumedBottom); 1272 | mTotalUnconsumedBottom = 0; 1273 | mBottomIsScrolling = false; 1274 | } 1275 | // Dispatch up our nested parent 1276 | stopNestedScroll(); 1277 | } 1278 | /** 1279 | * 这里是后于child的滑动 1280 | * */ 1281 | @Override 1282 | public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed, 1283 | final int dxUnconsumed, final int dyUnconsumed) { 1284 | // Dispatch up to the nested parent first 1285 | dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, 1286 | mParentOffsetInWindow); 1287 | Log.e(LOG_TAG,"onNestedScroll,dyUnconsumed="+dyUnconsumed+"mParentOffsetInWindow[1]"+mParentOffsetInWindow[1]); 1288 | // This is a bit of a hack. Nested scrolling works from the bottom up, and as we are 1289 | // sometimes between two nested scrolling views, we need a way to be able to know when any 1290 | // nested scrolling parent has stopped handling events. We do that by using the 1291 | // 'offset in window 'functionality to see if we have been moved from the event. 1292 | // This is a decent indication of whether we should take over the event stream or not. 1293 | final int dy = dyUnconsumed + mParentOffsetInWindow[1]; 1294 | if (dy < 0 && !canChildScrollUp() && !mRefreshingBottom) {//向下拉 1295 | mTotalUnconsumed += Math.abs(dy); 1296 | moveSpinner(mTotalUnconsumed); 1297 | } else if(dy > 0 && !canChildScrollDown() && !mRefreshing) //向上拉 1298 | { 1299 | mTotalUnconsumedBottom +=dy; 1300 | moveBottomSpinner(mTotalUnconsumedBottom); 1301 | mBottomIsScrolling = true; 1302 | } 1303 | } 1304 | 1305 | // NestedScrollingChild 1306 | 1307 | /** 1308 | * 子,设置嵌套滑动是否可用 1309 | * */ 1310 | @Override 1311 | public void setNestedScrollingEnabled(boolean enabled) { 1312 | mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled); 1313 | } 1314 | 1315 | /**嵌套滑动是否可用*/ 1316 | @Override 1317 | public boolean isNestedScrollingEnabled() { 1318 | return mNestedScrollingChildHelper.isNestedScrollingEnabled(); 1319 | } 1320 | 1321 | /** 1322 | * 开始嵌套滑动(子) 1323 | * axes表示方向,在ViewCompat.SCROLL_AXIS_HORIZONTAL横向 1324 | * 还有纵向 1325 | * 1326 | * 在这里面调用了mNestedScrollingChildHelper的startNestedScroll 1327 | * */ 1328 | @Override 1329 | public boolean startNestedScroll(int axes) { 1330 | return mNestedScrollingChildHelper.startNestedScroll(axes); 1331 | } 1332 | 1333 | /** 1334 | * 子,停止嵌套滑动 1335 | * */ 1336 | @Override 1337 | public void stopNestedScroll() { 1338 | mNestedScrollingChildHelper.stopNestedScroll(); 1339 | } 1340 | /** 1341 | * 是否有父view支持嵌套滑动 1342 | * */ 1343 | @Override 1344 | public boolean hasNestedScrollingParent() { 1345 | return mNestedScrollingChildHelper.hasNestedScrollingParent(); 1346 | } 1347 | /**在处理滑动之后调用 1348 | * @param dxConsumed x轴上 被消费的距离 1349 | * @param dyConsumed y轴上 被消费的距离 1350 | * @param dxUnconsumed x轴上 未被消费的距离 1351 | * @param dyUnconsumed y轴上 未被消费的距离 1352 | * @param offsetInWindow view 的移动距离 1353 | * */ 1354 | @Override 1355 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, 1356 | int dyUnconsumed, int[] offsetInWindow) { 1357 | //事先拦截 1358 | 1359 | return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, 1360 | dxUnconsumed, dyUnconsumed, offsetInWindow); 1361 | } 1362 | /** 1363 | * 一般在滑动之前调用, 在ontouch 中计算出滑动距离, 然后 调用改 方法, 就给支持的嵌套的父View 处理滑动事件 1364 | * @param dx x 轴上滑动的距离, 相对于上一次事件, 不是相对于 down事件的 那个距离 1365 | * @param dy y 轴上滑动的距离 1366 | * @param consumed 一个数组, 可以传 一个空的 数组, 表示 x 方向 或 y 方向的事件 是否有被消费 1367 | * @param offsetInWindow 支持嵌套滑动到额父View 消费 滑动事件后 导致 本 View 的移动距离 1368 | * @return 支持的嵌套的父View 是否处理了 滑动事件 1369 | */ 1370 | @Override 1371 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { 1372 | Log.e("fish","父:dispatchNestedPreScroll,dy="+dy); 1373 | //先拦截 1374 | if(mBottomIsScrolling && mTotalUnconsumedBottom>0 &&dy<0)//设置成功!但是子view还在动 1375 | { 1376 | Log.e("fish","父:dispatchNestedPreScroll,mTotalUnconsumedBottom="+mTotalUnconsumedBottom+",dy=="+dy); 1377 | if(-dy>=mTotalUnconsumedBottom)//向下拖动很大 1378 | { 1379 | moveBottomSpinner(mTotalUnconsumedBottom); 1380 | }else { 1381 | moveBottomSpinner(-dy); 1382 | mTotalUnconsumedBottom -= dy; 1383 | dy = 0; 1384 | } 1385 | } 1386 | return mNestedScrollingChildHelper.dispatchNestedPreScroll( 1387 | dx, dy, consumed, offsetInWindow); 1388 | } 1389 | 1390 | @Override 1391 | public boolean onNestedPreFling(View target, float velocityX, 1392 | float velocityY) { 1393 | return dispatchNestedPreFling(velocityX, velocityY); 1394 | } 1395 | 1396 | @Override 1397 | public boolean onNestedFling(View target, float velocityX, float velocityY, 1398 | boolean consumed) { 1399 | return dispatchNestedFling(velocityX, velocityY, consumed); 1400 | } 1401 | 1402 | @Override 1403 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 1404 | return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); 1405 | } 1406 | 1407 | @Override 1408 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 1409 | return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); 1410 | } 1411 | 1412 | private boolean isAnimationRunning(Animation animation) { 1413 | return animation != null && animation.hasStarted() && !animation.hasEnded(); 1414 | } 1415 | 1416 | 1417 | 1418 | 1419 | /** 1420 | * 进行实际circle的滑动绘制,包括颜色等设置 1421 | * 1422 | * @param overscrollTop 滑动的绝对值 1423 | * */ 1424 | private void moveBottomSpinner(float overscrollTop) { 1425 | mProgressBottom.showArrow(true); 1426 | float originalDragPercent = overscrollTop / mTotalDragDistance; 1427 | float dragPercent = Math.min(1f, Math.abs(originalDragPercent)); 1428 | float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;//这个不理解 1429 | float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;//这样不就是负的吗//就是负的//可以是正的 1430 | float slingshotDist = mUsingCustomStart ? mSpinnerOffsetEnd - mOriginalOffsetTop 1431 | : mSpinnerOffsetEnd;///mSpinnerOffsetEnd 默认是拉到最底的可能位置 ,和mTotalDragDistance一开始初始化是相同的 1432 | float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2) 1433 | / slingshotDist); 1434 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( 1435 | (tensionSlingshotPercent / 4), 2)) * 2f;//tensionSlingshotPercent = x ,x/4-(x/4)^2,再*2,最多0.5 1436 | float extraMove = (slingshotDist) * tensionPercent * 2;//这个是模拟后来的拖动,最多slingshotDist 1437 | 1438 | int targetY = mOriginalOffsetBottom - (int) ((slingshotDist * dragPercent) + extraMove); 1439 | 1440 | // where 1.0f is a full circle 1441 | if (mCircleViewBottom.getVisibility() != View.VISIBLE) { 1442 | mCircleViewBottom.setVisibility(View.VISIBLE); 1443 | } 1444 | if (!mScale) { 1445 | ViewCompat.setScaleX(mCircleViewBottom, 1f); 1446 | ViewCompat.setScaleY(mCircleViewBottom, 1f); 1447 | Log.e("param","mScale==false,in moveBottomSpinner"); 1448 | } 1449 | 1450 | if (mScale) { 1451 | setAnimationProgress(Math.min(1f, overscrollTop / mTotalDragDistance)); 1452 | } 1453 | if (overscrollTop < mTotalDragDistance) { 1454 | if (mProgressBottom.getAlpha() > STARTING_PROGRESS_ALPHA 1455 | && !isAnimationRunning(mAlphaStartAnimationBottom)) { 1456 | // Animate the alpha 1457 | startProgressAlphaStartAnimationBottom(); 1458 | } 1459 | } else { 1460 | if (mProgressBottom.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimationBottom)) { 1461 | // Animate the alpha 1462 | startProgressAlphaMaxAnimationBottom(); 1463 | } 1464 | } 1465 | float strokeStart = adjustedPercent * .8f; 1466 | mProgressBottom.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));//Trim 修剪 1467 | mProgressBottom.setArrowScale(Math.min(1f, adjustedPercent)); 1468 | 1469 | float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; 1470 | mProgressBottom.setProgressRotation(rotation); 1471 | setTargetOffsetTopAndBottomForBottom(targetY-mCurrentTargetOffsetBottom, true /* requires update */); 1472 | 1473 | 1474 | } 1475 | 1476 | 1477 | 1478 | /**进行实际circle的滑动绘制,包括颜色等设置*/ 1479 | private void moveSpinner(float overscrollTop) { 1480 | Log.e("moveSpinner","+++++++++++++++++++++++moveSpinner--"+overscrollTop); 1481 | //overscrollTop = -overscrollTop; 1482 | mProgress.showArrow(true); 1483 | float originalDragPercent = overscrollTop / mTotalDragDistance; 1484 | Log.e("moveSpinner","moveSpinner--originalDragPercent=="+originalDragPercent); 1485 | float dragPercent = Math.min(1f, Math.abs(originalDragPercent)); 1486 | Log.e("moveSpinner","moveSpinner--dragPercent=="+dragPercent); 1487 | float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;//这个不理解 1488 | Log.e("moveSpinner","moveSpinner--adjustedPercent=="+adjustedPercent); 1489 | float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;//这样不就是负的吗//就是负的//可以是正的 1490 | Log.e("moveSpinner","moveSpinner--extraOS=="+extraOS); 1491 | float slingshotDist = mUsingCustomStart ? mSpinnerOffsetEnd - mOriginalOffsetTop 1492 | : mSpinnerOffsetEnd;///mSpinnerOffsetEnd 默认是拉到最底的可能位置 ,和mTotalDragDistance一开始初始化是相同的 1493 | Log.e("moveSpinner","moveSpinner--slingshotDist=="+slingshotDist+",mSpinnerOffsetEnd=="+mSpinnerOffsetEnd); 1494 | float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2) 1495 | / slingshotDist); 1496 | Log.e("moveSpinner","moveSpinner--tensionSlingshotPercent=="+tensionSlingshotPercent); 1497 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( 1498 | (tensionSlingshotPercent / 4), 2)) * 2f;//tensionSlingshotPercent = x ,x/4-(x/4)^2,再*2 最大1/2 1499 | Log.e("moveSpinner","moveSpinner--tensionPercent=="+tensionPercent); 1500 | float extraMove = (slingshotDist) * tensionPercent * 2;//我理解了,这个是模拟后来的拖动,如果 1501 | Log.e("moveSpinner","moveSpinner--extraMove=="+extraMove); 1502 | int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);//最大可以达到 +2 slingshotDist 1503 | Log.e("moveSpinner","moveSpinner--targetY=="+targetY); 1504 | 1505 | 1506 | // where 1.0f is a full circle 1507 | if (mCircleView.getVisibility() != View.VISIBLE) { 1508 | mCircleView.setVisibility(View.VISIBLE); 1509 | } 1510 | if (!mScale) { 1511 | ViewCompat.setScaleX(mCircleView, 1f); 1512 | ViewCompat.setScaleY(mCircleView, 1f); 1513 | } 1514 | 1515 | if (mScale) { 1516 | setAnimationProgress(Math.min(1f, overscrollTop / mTotalDragDistance)); 1517 | } 1518 | if (overscrollTop < mTotalDragDistance) { 1519 | if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA 1520 | && !isAnimationRunning(mAlphaStartAnimation)) { 1521 | // Animate the alpha 1522 | startProgressAlphaStartAnimation(); 1523 | } 1524 | } else { 1525 | if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) { 1526 | // Animate the alpha 1527 | startProgressAlphaMaxAnimation(); 1528 | } 1529 | } 1530 | float strokeStart = adjustedPercent * .8f; 1531 | mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); 1532 | mProgress.setArrowScale(Math.min(1f, adjustedPercent)); 1533 | 1534 | float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; 1535 | mProgress.setProgressRotation(rotation); 1536 | setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */); 1537 | Log.e("moveSpinner","+++++++++++++++++++++++moveSpinner--move=="+(targetY - mCurrentTargetOffsetTop)); 1538 | // setTargetOffsetTopAndBottom(10, true /* requires update */); 1539 | // Log.e(LOG_TAG,"mCurrentTargetOffsetTop=="+mCurrentTargetOffsetTop+",targetY=="+targetY); 1540 | } 1541 | 1542 | private void finishSpinner(float overscrollTop) { 1543 | Log.e("fish","finishSpinner--"+overscrollTop); 1544 | if (overscrollTop > mTotalDragDistance) {//大于这个核心点就升上去 1545 | setRefreshing(true, true /* notify */); 1546 | Log.e("fish","finishSpinner-->>>overscrollTop > mTotalDragDistance"); 1547 | } else { 1548 | // cancel refresh 1549 | mRefreshing = false; 1550 | mProgress.setStartEndTrim(0f, 0f); 1551 | AnimationListener listener = null; 1552 | if (!mScale) { 1553 | listener = new AnimationListener() { 1554 | 1555 | @Override 1556 | public void onAnimationStart(Animation animation) { 1557 | } 1558 | 1559 | @Override 1560 | public void onAnimationEnd(Animation animation) { 1561 | if (!mScale) { 1562 | startScaleDownAnimation(null); 1563 | } 1564 | } 1565 | 1566 | @Override 1567 | public void onAnimationRepeat(Animation animation) { 1568 | } 1569 | 1570 | }; 1571 | } 1572 | animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener); 1573 | mProgress.showArrow(false); 1574 | } 1575 | } 1576 | 1577 | /**结束下半部的spinner*/ 1578 | private void finishSpinnerBottom(float overscrollTop) {//在小于mTotalDragDistance的时候处理的不好,会在中间停止 1579 | Log.e("fish","finishSpinnerBottom--"+overscrollTop); 1580 | if (overscrollTop > mTotalDragDistance) { 1581 | setRefreshingBottom(true, true); 1582 | Log.e("fish","finishSpinnerBottom-->>>overscrollTop > mTotalDragDistance"); 1583 | } else { 1584 | // cancel refresh 1585 | mRefreshingBottom = false; 1586 | mProgressBottom.setStartEndTrim(0f, 0f); 1587 | AnimationListener listener = null; 1588 | if (!mScale) { 1589 | listener = new AnimationListener() { 1590 | 1591 | @Override 1592 | public void onAnimationStart(Animation animation) { 1593 | } 1594 | 1595 | @Override 1596 | public void onAnimationEnd(Animation animation) { 1597 | if (!mScale) { 1598 | // startScaleDownAnimation(null); 1599 | startScaleDownAnimationBottom(null);//倒着转 1600 | } 1601 | } 1602 | 1603 | @Override 1604 | public void onAnimationRepeat(Animation animation) { 1605 | } 1606 | 1607 | }; 1608 | } 1609 | // animateOffsetToCorrectPositionBottom(mCurrentTargetOffsetBottom,listener); 1610 | animateOffsetToStartPositionBottom(mCurrentTargetOffsetBottom,listener); 1611 | mProgressBottom.showArrow(false); 1612 | } 1613 | } 1614 | 1615 | 1616 | 1617 | 1618 | @Override 1619 | public boolean onTouchEvent(MotionEvent ev) { 1620 | final int action = MotionEventCompat.getActionMasked(ev); 1621 | int pointerIndex = -1; 1622 | 1623 | if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { 1624 | mReturningToStart = false; 1625 | } 1626 | Log.e("fish","-onTouchEvent-;ACTION=="+ev.getAction()); 1627 | if (!isEnabled() || mReturningToStart || canChildScrollUp() 1628 | || mRefreshing || mNestedScrollInProgress) { 1629 | // Fail fast if we're not in a state where a swipe is possible 1630 | return false; 1631 | } 1632 | 1633 | switch (action) { 1634 | case MotionEvent.ACTION_DOWN: 1635 | Log.e("fish","-onTouchEvent-;ACTION_DOWN"); 1636 | mActivePointerId = ev.getPointerId(0); 1637 | mIsBeingDragged = false; 1638 | break; 1639 | 1640 | case MotionEvent.ACTION_MOVE: { 1641 | Log.e("fish","-onTouchEvent-;ACTION_MOVE"); 1642 | pointerIndex = ev.findPointerIndex(mActivePointerId); 1643 | if (pointerIndex < 0) { 1644 | Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id."); 1645 | return false; 1646 | } 1647 | 1648 | final float y = ev.getY(pointerIndex); 1649 | startDragging(y); 1650 | 1651 | if (mIsBeingDragged) { 1652 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 1653 | if (overscrollTop > 0 && !mRefreshingBottom) { 1654 | moveSpinner(overscrollTop*2); 1655 | } else { 1656 | return false; 1657 | } 1658 | } 1659 | break; 1660 | } 1661 | case MotionEventCompat.ACTION_POINTER_DOWN: { 1662 | Log.e("fish","-onTouchEvent-;MotionEventCompat.ACTION_POINTER_DOWN"); 1663 | pointerIndex = MotionEventCompat.getActionIndex(ev); 1664 | if (pointerIndex < 0) { 1665 | Log.e(LOG_TAG, 1666 | "Got ACTION_POINTER_DOWN event but have an invalid action index."); 1667 | return false; 1668 | } 1669 | mActivePointerId = ev.getPointerId(pointerIndex); 1670 | break; 1671 | } 1672 | 1673 | case MotionEventCompat.ACTION_POINTER_UP: 1674 | Log.e("fish","-onTouchEvent-;MotionEventCompat.ACTION_POINTER_UP"); 1675 | onSecondaryPointerUp(ev); 1676 | break; 1677 | 1678 | case MotionEvent.ACTION_UP: { 1679 | Log.e("fish","-onTouchEvent-;MotionEvent.ACTION_UP"); 1680 | pointerIndex = ev.findPointerIndex(mActivePointerId); 1681 | if (pointerIndex < 0) { 1682 | Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id."); 1683 | return false; 1684 | } 1685 | 1686 | if (mIsBeingDragged && !mRefreshingBottom) { 1687 | final float y = ev.getY(pointerIndex); 1688 | final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE; 1689 | mIsBeingDragged = false; 1690 | finishSpinner(overscrollTop); 1691 | } 1692 | mActivePointerId = INVALID_POINTER; 1693 | return false; 1694 | } 1695 | case MotionEvent.ACTION_CANCEL: 1696 | return false; 1697 | } 1698 | 1699 | return true; 1700 | } 1701 | 1702 | private void startDragging(float y) { 1703 | final float yDiff = y - mInitialDownY; 1704 | if (yDiff > mTouchSlop && !mIsBeingDragged) { 1705 | mInitialMotionY = mInitialDownY + mTouchSlop; 1706 | mIsBeingDragged = true; 1707 | mProgress.setAlpha(STARTING_PROGRESS_ALPHA); 1708 | } 1709 | } 1710 | /**启动关于mCircleView的动画mAnimateToCorrectPosition,相应的更改mFrom的值为 1711 | * param:from 1712 | * */ 1713 | private void animateOffsetToCorrectPosition(int from, AnimationListener listener) { 1714 | mFrom = from; 1715 | mAnimateToCorrectPosition.reset(); 1716 | mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION); 1717 | mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator); 1718 | if (listener != null) { 1719 | mCircleView.setAnimationListener(listener); 1720 | } 1721 | mCircleView.clearAnimation(); 1722 | mCircleView.startAnimation(mAnimateToCorrectPosition); 1723 | } 1724 | 1725 | 1726 | /**启动关于mCircleView的动画mAnimateToCorrectPosition,相应的更改mFrom的值为 1727 | * param:from 1728 | * */ 1729 | private void animateOffsetToCorrectPositionBottom(int from, AnimationListener listener) { 1730 | mFrom = from; 1731 | mAnimateToCorrectPositionBottom.reset(); 1732 | mAnimateToCorrectPositionBottom.setDuration(ANIMATE_TO_TRIGGER_DURATION); 1733 | mAnimateToCorrectPositionBottom.setInterpolator(mDecelerateInterpolator); 1734 | if (listener != null) { 1735 | mCircleViewBottom.setAnimationListener(listener); 1736 | } 1737 | mCircleViewBottom.clearAnimation(); 1738 | mCircleViewBottom.startAnimation(mAnimateToCorrectPositionBottom); 1739 | } 1740 | 1741 | 1742 | 1743 | 1744 | private void animateOffsetToStartPosition(int from, AnimationListener listener) { 1745 | if (mScale) { 1746 | // Scale the item back down 1747 | startScaleDownReturnToStartAnimation(from, listener); 1748 | } else { 1749 | mFrom = from; 1750 | mAnimateToStartPosition.reset(); 1751 | mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION); 1752 | mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator); 1753 | if (listener != null) { 1754 | mCircleView.setAnimationListener(listener); 1755 | } 1756 | mCircleView.clearAnimation(); 1757 | mCircleView.startAnimation(mAnimateToStartPosition); 1758 | } 1759 | } 1760 | 1761 | 1762 | private void animateOffsetToStartPositionBottom(int from, AnimationListener listener) { 1763 | if (mScale) { 1764 | // Scale the item back down 1765 | startScaleDownReturnToStartAnimation(from, listener); 1766 | } else { 1767 | mFrom = from; 1768 | mAnimateToStartPositionBottom.reset(); 1769 | mAnimateToStartPositionBottom.setDuration(ANIMATE_TO_START_DURATION); 1770 | mAnimateToStartPositionBottom.setInterpolator(mDecelerateInterpolator); 1771 | if (listener != null) { 1772 | mCircleViewBottom.setAnimationListener(listener); 1773 | } 1774 | mCircleViewBottom.clearAnimation(); 1775 | mCircleViewBottom.startAnimation(mAnimateToStartPositionBottom); 1776 | } 1777 | } 1778 | 1779 | 1780 | /**刷新的动画,将circle从mFrom动画移动到mSpinnerOffsetEnd 1781 | * 1782 | * */ 1783 | private final Animation mAnimateToCorrectPosition = new Animation() { 1784 | @Override 1785 | public void applyTransformation(float interpolatedTime, Transformation t) { 1786 | int targetTop = 0; 1787 | int endTarget = 0; 1788 | if (!mUsingCustomStart) { 1789 | endTarget = mSpinnerOffsetEnd - Math.abs(mOriginalOffsetTop); 1790 | } else { 1791 | endTarget = mSpinnerOffsetEnd; 1792 | } 1793 | targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));//一开始我这里有疑惑,但是后来才想到interpolatedTime是0.x的。也就相当于百分比 1794 | int offset = targetTop - mCircleView.getTop(); 1795 | setTargetOffsetTopAndBottom(offset, false /* requires update */); 1796 | mProgress.setArrowScale(1 - interpolatedTime); 1797 | } 1798 | }; 1799 | 1800 | 1801 | 1802 | /**刷新的动画,将circle从mFrom移动到 1803 | *中间然后滑动 1804 | * Bottom 1805 | * */ 1806 | private final Animation mAnimateToCorrectPositionBottom = new Animation() { 1807 | @Override 1808 | public void applyTransformation(float interpolatedTime, Transformation t) { 1809 | int targetTop = 0; 1810 | int endTarget = 0; 1811 | if (!mUsingCustomStart) {//false 1812 | endTarget = (int) (mOriginalOffsetBottom - mSpinnerOffsetEnd*1.5);//*2 - mCircleDiameter;//mOriginalOffsetTop初始是负直径 1813 | } else { 1814 | endTarget = mSpinnerOffsetEnd; 1815 | } 1816 | targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));//一开始我这里有疑惑,但是后来才想到interpolatedTime是0.x的。也就相当于百分比 1817 | int offset = targetTop - mCircleViewBottom.getBottom(); 1818 | // Lo 1819 | // setTargetOffsetTopAndBottom(offset, false /* requires update */); 1820 | setTargetOffsetTopAndBottomForBottom(offset,false); 1821 | mProgress.setArrowScale(1 - interpolatedTime); 1822 | } 1823 | }; 1824 | 1825 | /**回到顶部 1826 | * @param interpolatedTime 动画时间 1827 | * @return 这里是根据当前位置 mFrom 回到 mOriginalOffsetTop 1828 | * 1829 | * */ 1830 | void moveToStart(float interpolatedTime) { 1831 | int targetTop = 0; 1832 | targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime)); 1833 | int offset = targetTop - mCircleView.getTop(); 1834 | setTargetOffsetTopAndBottom(offset, false /* requires update */); 1835 | } 1836 | 1837 | /**回到顶部 1838 | * @param interpolatedTime 动画时间 1839 | * @return 这里是根据当前位置 mFrom 回到 mOriginalOffsetTop 1840 | * 1841 | * */ 1842 | void moveToEnd(float interpolatedTime) { 1843 | int targetTop = 0; 1844 | targetTop = (mFrom + (int) ((mOriginalOffsetBottom - mFrom) * interpolatedTime)); 1845 | int offset = targetTop - mCircleViewBottom.getBottom(); 1846 | setTargetOffsetTopAndBottomForBottom(offset, false /* requires update */); 1847 | } 1848 | 1849 | 1850 | private final Animation mAnimateToStartPosition = new Animation() { 1851 | @Override 1852 | public void applyTransformation(float interpolatedTime, Transformation t) { 1853 | moveToStart(interpolatedTime); 1854 | } 1855 | }; 1856 | 1857 | private final Animation mAnimateToStartPositionBottom = new Animation() { 1858 | @Override 1859 | public void applyTransformation(float interpolatedTime, Transformation t) { 1860 | moveToEnd(interpolatedTime); 1861 | } 1862 | }; 1863 | 1864 | 1865 | private void startScaleDownReturnToStartAnimation(int from, 1866 | AnimationListener listener) { 1867 | mFrom = from; 1868 | if (isAlphaUsedForScale()) { 1869 | mStartingScale = mProgress.getAlpha(); 1870 | } else { 1871 | mStartingScale = ViewCompat.getScaleX(mCircleView); 1872 | } 1873 | mScaleDownToStartAnimation = new Animation() { 1874 | @Override 1875 | public void applyTransformation(float interpolatedTime, Transformation t) { 1876 | float targetScale = (mStartingScale + (-mStartingScale * interpolatedTime)); 1877 | setAnimationProgress(targetScale); 1878 | moveToStart(interpolatedTime); 1879 | } 1880 | }; 1881 | mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION); 1882 | if (listener != null) { 1883 | mCircleView.setAnimationListener(listener); 1884 | } 1885 | mCircleView.clearAnimation(); 1886 | mCircleView.startAnimation(mScaleDownToStartAnimation); 1887 | } 1888 | 1889 | 1890 | /** 1891 | * 首先,提升circle的视图,然后将ciecle向上推offset个距离 1892 | *其中用到的是ViewCompat.offsetTopAndBottom(View,offset);正数向下 负数向上 1893 | *当requiresUpdate为true时,会进行整个的重绘,如果sdk小于11 1894 | *

注意,每次移动的时候都会更新mCircleViewBottom

1895 | * */ 1896 | void setTargetOffsetTopAndBottomForBottom(int offset, boolean requiresUpdate) { 1897 | mCircleViewBottom.bringToFront();//改变circle在俯视图中的位置即z坐标,将他提到最上面来 1898 | //通过offset直接改变坐标,正数向下负数向上 1899 | ViewCompat.offsetTopAndBottom(mCircleViewBottom, offset); 1900 | mCurrentTargetOffsetBottom = mCircleViewBottom.getBottom(); 1901 | mCircleViewBottom.invalidate(); 1902 | if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) { 1903 | invalidate(); 1904 | } 1905 | } 1906 | 1907 | 1908 | 1909 | /** 1910 | * 首先,提升circle的视图,然后将ciecle向下推offset个距离 1911 | *其中用到的是ViewCompat.offsetTopAndBottom(View,offset);正数向下负数向上 1912 | *当requiresUpdate为true时,会进行整个的重绘,如果sdk小于11 1913 | * */ 1914 | void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) { 1915 | Log.e(LOG_TAG,"setTargetOffsetTopAndBottom,offset=="+offset); 1916 | mCircleView.bringToFront();//改变circle在俯视图中的位置即z坐标,将他提到最上面来 1917 | //通过offset直接改变坐标,正数向下负数向上 1918 | ViewCompat.offsetTopAndBottom(mCircleView, offset); 1919 | mCurrentTargetOffsetTop = mCircleView.getTop(); 1920 | mCircleView.invalidate(); 1921 | if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) { 1922 | invalidate(); 1923 | } 1924 | } 1925 | 1926 | private void onSecondaryPointerUp(MotionEvent ev) { 1927 | final int pointerIndex = MotionEventCompat.getActionIndex(ev); 1928 | final int pointerId = ev.getPointerId(pointerIndex); 1929 | if (pointerId == mActivePointerId) { 1930 | // This was our active pointer going up. Choose a new 1931 | // active pointer and adjust accordingly. 1932 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1933 | mActivePointerId = ev.getPointerId(newPointerIndex); 1934 | } 1935 | } 1936 | 1937 | /** 1938 | * Classes that wish to be notified when the swipe gesture correctly 1939 | * triggers a refresh should implement this interface. 1940 | */ 1941 | public interface OnRefreshListener { 1942 | /** 1943 | * Called when a swipe gesture triggers a refresh. 1944 | */ 1945 | void onRefresh(); 1946 | } 1947 | 1948 | /**自建的下部刷新接口*/ 1949 | public interface OnBottomRefreshListener{ 1950 | void onBottomRefresh(); 1951 | } 1952 | 1953 | /** 1954 | * Classes that wish to override {@link GZoomSwifrefresh#canChildScrollUp()} method 1955 | * behavior should implement this interface. 1956 | */ 1957 | public interface OnChildScrollUpCallback { 1958 | /** 1959 | * Callback that will be called when {@link GZoomSwifrefresh#canChildScrollUp()} method 1960 | * is called to allow the implementer to override its behavior. 1961 | * 1962 | * @param parent GZoomSwifrefresh that this callback is overriding. 1963 | * @param child The child view of GZoomSwifrefresh. 1964 | * 1965 | * @return Whether it is possible for the child view of parent layout to scroll up. 1966 | */ 1967 | boolean canChildScrollUp(GZoomSwifrefresh parent, @Nullable View child); 1968 | } 1969 | } 1970 | -------------------------------------------------------------------------------- /widgetpro/src/main/java/gzoomswiperefresh/MainActivity.java: -------------------------------------------------------------------------------- 1 | package gzoomswiperefresh; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.design.widget.NavigationView; 6 | import android.support.design.widget.TabLayout; 7 | import android.support.v4.app.Fragment; 8 | import android.support.v4.view.ViewPager; 9 | import android.support.v4.widget.DrawerLayout; 10 | import android.support.v7.app.ActionBarDrawerToggle; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.support.v7.widget.Toolbar; 13 | import android.view.MenuItem; 14 | 15 | import com.example.widgetpro.R; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | import butterknife.Bind; 21 | import butterknife.ButterKnife; 22 | 23 | public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener{ 24 | 25 | @Bind(R.id.main_toolbar) 26 | Toolbar toolbar; 27 | 28 | @Bind(R.id.main_drawerlayout) 29 | DrawerLayout drawerLayout; 30 | 31 | @Bind(R.id.main_navigation) 32 | NavigationView navigationView; 33 | 34 | @Bind(R.id.main_tablayout) 35 | TabLayout tabLayout; 36 | 37 | @Bind(R.id.main_viewpager) 38 | ViewPager viewpager; 39 | 40 | Listfrag_list=new ArrayList<>(); 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.own_main); 45 | ButterKnife.bind(this); 46 | setSupportActionBar(toolbar); 47 | 48 | ActionBarDrawerToggle toggle = new ActionBarDrawerToggle 49 | (this,drawerLayout,toolbar,R.string.navigation_drawer_open,R.string.navigation_drawer_open); 50 | 51 | drawerLayout.addDrawerListener(toggle);//set方法已经过时了 52 | toggle.syncState(); 53 | navigationView.setNavigationItemSelectedListener(this); 54 | initTablayout(); 55 | } 56 | 57 | /**初始化tab的内容*/ 58 | private void initTablayout() { 59 | Listtabs = new ArrayList<>(); 60 | tabs.add("testFragemnt"); 61 | ApiShowFragment f1 = new ApiShowFragment(); 62 | frag_list.add(f1); 63 | TabFragmentAdapter adapter = new TabFragmentAdapter(getSupportFragmentManager(),frag_list,tabs); 64 | viewpager.setAdapter(adapter); 65 | tabLayout.setupWithViewPager(viewpager); 66 | tabLayout.setTabsFromPagerAdapter(adapter); 67 | 68 | } 69 | 70 | @Override 71 | public boolean onNavigationItemSelected(@NonNull MenuItem item) { 72 | return false; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /widgetpro/src/main/java/gzoomswiperefresh/MaterialProgressDrawable.java: -------------------------------------------------------------------------------- 1 | package gzoomswiperefresh; 2 | 3 | /** 4 | * Created by gzoom on 2017/2/4. 5 | *//* 6 | * Copyright (C) 2014 The Android Open Source Project 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | import android.content.Context; 22 | import android.content.res.Resources; 23 | import android.graphics.Canvas; 24 | import android.graphics.Color; 25 | import android.graphics.ColorFilter; 26 | import android.graphics.Paint; 27 | import android.graphics.Path; 28 | import android.graphics.PixelFormat; 29 | import android.graphics.Rect; 30 | import android.graphics.RectF; 31 | import android.graphics.drawable.Animatable; 32 | import android.graphics.drawable.Drawable; 33 | import android.support.annotation.IntDef; 34 | import android.support.annotation.NonNull; 35 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 36 | import android.util.DisplayMetrics; 37 | import android.view.View; 38 | import android.view.animation.Animation; 39 | import android.view.animation.Interpolator; 40 | import android.view.animation.LinearInterpolator; 41 | import android.view.animation.Transformation; 42 | 43 | import java.lang.annotation.Retention; 44 | import java.lang.annotation.RetentionPolicy; 45 | import java.util.ArrayList; 46 | 47 | 48 | /** 49 | * Fancy progress indicator for Material theme. 50 | */ 51 | class MaterialProgressDrawable extends Drawable implements Animatable { 52 | private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 53 | static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); 54 | 55 | private static final float FULL_ROTATION = 1080.0f; 56 | 57 | @Retention(RetentionPolicy.SOURCE) 58 | @IntDef({LARGE, DEFAULT}) 59 | public @interface ProgressDrawableSize {} 60 | 61 | // Maps to ProgressBar.Large style 62 | static final int LARGE = 0; 63 | // Maps to ProgressBar default style 64 | static final int DEFAULT = 1; 65 | 66 | // Maps to ProgressBar default style 67 | private static final int CIRCLE_DIAMETER = 40; 68 | private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width 69 | private static final float STROKE_WIDTH = 2.5f; 70 | 71 | // Maps to ProgressBar.Large style 72 | private static final int CIRCLE_DIAMETER_LARGE = 56; 73 | private static final float CENTER_RADIUS_LARGE = 12.5f; 74 | private static final float STROKE_WIDTH_LARGE = 3f; 75 | 76 | private static final int[] COLORS = new int[] { 77 | Color.BLACK 78 | }; 79 | 80 | /** 81 | * The value in the linear interpolator for animating the drawable at which 82 | * the color transition should start 83 | */ 84 | private static final float COLOR_START_DELAY_OFFSET = 0.75f; 85 | private static final float END_TRIM_START_DELAY_OFFSET = 0.5f; 86 | private static final float START_TRIM_DURATION_OFFSET = 0.5f; 87 | 88 | /** The duration of a single progress spin in milliseconds. */ 89 | private static final int ANIMATION_DURATION = 1332; 90 | 91 | /** The number of points in the progress "star". */ 92 | private static final float NUM_POINTS = 5f; 93 | /** The list of animators operating on this drawable. */ 94 | private final ArrayList mAnimators = new ArrayList(); 95 | 96 | /** The indicator ring, used to manage animation state. */ 97 | private final MaterialProgressDrawable.Ring mRing; 98 | 99 | /** Canvas rotation in degrees. */ 100 | private float mRotation; 101 | 102 | /** Layout info for the arrowhead in dp */ 103 | private static final int ARROW_WIDTH = 10; 104 | private static final int ARROW_HEIGHT = 5; 105 | private static final float ARROW_OFFSET_ANGLE = 5; 106 | 107 | /** Layout info for the arrowhead for the large spinner in dp */ 108 | private static final int ARROW_WIDTH_LARGE = 12; 109 | private static final int ARROW_HEIGHT_LARGE = 6; 110 | private static final float MAX_PROGRESS_ARC = .8f; 111 | 112 | private Resources mResources; 113 | private View mParent; 114 | private Animation mAnimation; 115 | float mRotationCount; 116 | private double mWidth; 117 | private double mHeight; 118 | boolean mFinishing; 119 | 120 | MaterialProgressDrawable(Context context, View parent) { 121 | mParent = parent; 122 | mResources = context.getResources(); 123 | 124 | mRing = new MaterialProgressDrawable.Ring(mCallback); 125 | mRing.setColors(COLORS); 126 | 127 | updateSizes(DEFAULT); 128 | setupAnimators(); 129 | } 130 | 131 | private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, 132 | double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { 133 | final MaterialProgressDrawable.Ring ring = mRing; 134 | final DisplayMetrics metrics = mResources.getDisplayMetrics(); 135 | final float screenDensity = metrics.density; 136 | 137 | mWidth = progressCircleWidth * screenDensity; 138 | mHeight = progressCircleHeight * screenDensity; 139 | ring.setStrokeWidth((float) strokeWidth * screenDensity); 140 | ring.setCenterRadius(centerRadius * screenDensity); 141 | ring.setColorIndex(0); 142 | ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); 143 | ring.setInsets((int) mWidth, (int) mHeight); 144 | } 145 | 146 | /** 147 | * Set the overall size for the progress spinner. This updates the radius 148 | * and stroke width of the ring. 149 | */ 150 | public void updateSizes(int size) { 151 | if (size == LARGE) { 152 | setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, 153 | STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); 154 | } else { 155 | setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, 156 | ARROW_WIDTH, ARROW_HEIGHT); 157 | } 158 | } 159 | 160 | /** 161 | * @param show Set to true to display the arrowhead on the progress spinner. 162 | */ 163 | public void showArrow(boolean show) { 164 | mRing.setShowArrow(show); 165 | } 166 | 167 | /** 168 | * @param scale Set the scale of the arrowhead for the spinner. 169 | */ 170 | public void setArrowScale(float scale) { 171 | mRing.setArrowScale(scale); 172 | } 173 | 174 | /** 175 | * Set the start and end trim for the progress spinner arc. 176 | * 177 | * @param startAngle start angle 178 | * @param endAngle end angle 179 | */ 180 | public void setStartEndTrim(float startAngle, float endAngle) { 181 | mRing.setStartTrim(startAngle); 182 | mRing.setEndTrim(endAngle); 183 | } 184 | 185 | /** 186 | * Set the amount of rotation to apply to the progress spinner. 187 | * 188 | * @param rotation Rotation is from [0..1] 189 | */ 190 | public void setProgressRotation(float rotation) { 191 | mRing.setRotation(rotation); 192 | } 193 | 194 | /** 195 | * Update the background color of the circle image view. 196 | */ 197 | public void setBackgroundColor(int color) { 198 | mRing.setBackgroundColor(color); 199 | } 200 | 201 | /** 202 | * Set the colors used in the progress animation from color resources. 203 | * The first color will also be the color of the bar that grows in response 204 | * to a user swipe gesture. 205 | * 206 | * @param colors 207 | */ 208 | public void setColorSchemeColors(int... colors) { 209 | mRing.setColors(colors); 210 | mRing.setColorIndex(0); 211 | } 212 | 213 | @Override 214 | public int getIntrinsicHeight() { 215 | return (int) mHeight; 216 | } 217 | 218 | @Override 219 | public int getIntrinsicWidth() { 220 | return (int) mWidth; 221 | } 222 | 223 | @Override 224 | public void draw(Canvas c) { 225 | final Rect bounds = getBounds(); 226 | final int saveCount = c.save(); 227 | c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); 228 | mRing.draw(c, bounds); 229 | c.restoreToCount(saveCount); 230 | } 231 | 232 | @Override 233 | public void setAlpha(int alpha) { 234 | mRing.setAlpha(alpha); 235 | } 236 | 237 | public int getAlpha() { 238 | return mRing.getAlpha(); 239 | } 240 | 241 | @Override 242 | public void setColorFilter(ColorFilter colorFilter) { 243 | mRing.setColorFilter(colorFilter); 244 | } 245 | 246 | @SuppressWarnings("unused") 247 | void setRotation(float rotation) { 248 | mRotation = rotation; 249 | invalidateSelf(); 250 | } 251 | 252 | @SuppressWarnings("unused") 253 | private float getRotation() { 254 | return mRotation; 255 | } 256 | 257 | @Override 258 | public int getOpacity() { 259 | return PixelFormat.TRANSLUCENT; 260 | } 261 | 262 | @Override 263 | public boolean isRunning() { 264 | final ArrayList animators = mAnimators; 265 | final int N = animators.size(); 266 | for (int i = 0; i < N; i++) { 267 | final Animation animator = animators.get(i); 268 | if (animator.hasStarted() && !animator.hasEnded()) { 269 | return true; 270 | } 271 | } 272 | return false; 273 | } 274 | 275 | @Override 276 | public void start() { 277 | mAnimation.reset(); 278 | mRing.storeOriginals(); 279 | // Already showing some part of the ring 280 | if (mRing.getEndTrim() != mRing.getStartTrim()) { 281 | mFinishing = true; 282 | mAnimation.setDuration(ANIMATION_DURATION / 2); 283 | mParent.startAnimation(mAnimation); 284 | } else { 285 | mRing.setColorIndex(0); 286 | mRing.resetOriginals(); 287 | mAnimation.setDuration(ANIMATION_DURATION); 288 | mParent.startAnimation(mAnimation); 289 | } 290 | } 291 | 292 | @Override 293 | public void stop() { 294 | mParent.clearAnimation(); 295 | setRotation(0); 296 | mRing.setShowArrow(false); 297 | mRing.setColorIndex(0); 298 | mRing.resetOriginals(); 299 | } 300 | 301 | float getMinProgressArc(MaterialProgressDrawable.Ring ring) { 302 | return (float) Math.toRadians( 303 | ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius())); 304 | } 305 | 306 | // Adapted from ArgbEvaluator.java 307 | private int evaluateColorChange(float fraction, int startValue, int endValue) { 308 | int startInt = (Integer) startValue; 309 | int startA = (startInt >> 24) & 0xff; 310 | int startR = (startInt >> 16) & 0xff; 311 | int startG = (startInt >> 8) & 0xff; 312 | int startB = startInt & 0xff; 313 | 314 | int endInt = (Integer) endValue; 315 | int endA = (endInt >> 24) & 0xff; 316 | int endR = (endInt >> 16) & 0xff; 317 | int endG = (endInt >> 8) & 0xff; 318 | int endB = endInt & 0xff; 319 | 320 | return (int) ((startA + (int) (fraction * (endA - startA))) << 24) 321 | | (int) ((startR + (int) (fraction * (endR - startR))) << 16) 322 | | (int) ((startG + (int) (fraction * (endG - startG))) << 8) 323 | | (int) ((startB + (int) (fraction * (endB - startB)))); 324 | } 325 | 326 | /** 327 | * Update the ring color if this is within the last 25% of the animation. 328 | * The new ring color will be a translation from the starting ring color to 329 | * the next color. 330 | */ 331 | void updateRingColor(float interpolatedTime,MaterialProgressDrawable.Ring ring) { 332 | if (interpolatedTime > COLOR_START_DELAY_OFFSET) { 333 | // scale the interpolatedTime so that the full 334 | // transformation from 0 - 1 takes place in the 335 | // remaining time 336 | ring.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET) 337 | / (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(), 338 | ring.getNextColor())); 339 | } 340 | } 341 | 342 | void applyFinishTranslation(float interpolatedTime, MaterialProgressDrawable.Ring ring) { 343 | // shrink back down and complete a full rotation before 344 | // starting other circles 345 | // Rotation goes between [0..1]. 346 | updateRingColor(interpolatedTime, ring); 347 | float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC) 348 | + 1f); 349 | final float minProgressArc = getMinProgressArc(ring); 350 | final float startTrim = ring.getStartingStartTrim() 351 | + (ring.getStartingEndTrim() - minProgressArc - ring.getStartingStartTrim()) 352 | * interpolatedTime; 353 | ring.setStartTrim(startTrim); 354 | ring.setEndTrim(ring.getStartingEndTrim()); 355 | final float rotation = ring.getStartingRotation() 356 | + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); 357 | ring.setRotation(rotation); 358 | } 359 | 360 | private void setupAnimators() { 361 | final MaterialProgressDrawable.Ring ring = mRing; 362 | final Animation animation = new Animation() { 363 | @Override 364 | public void applyTransformation(float interpolatedTime, Transformation t) { 365 | if (mFinishing) { 366 | applyFinishTranslation(interpolatedTime, ring); 367 | } else { 368 | // The minProgressArc is calculated from 0 to create an 369 | // angle that matches the stroke width. 370 | final float minProgressArc = getMinProgressArc(ring); 371 | final float startingEndTrim = ring.getStartingEndTrim(); 372 | final float startingTrim = ring.getStartingStartTrim(); 373 | final float startingRotation = ring.getStartingRotation(); 374 | 375 | updateRingColor(interpolatedTime, ring); 376 | 377 | // Moving the start trim only occurs in the first 50% of a 378 | // single ring animation 379 | if (interpolatedTime <= START_TRIM_DURATION_OFFSET) { 380 | // scale the interpolatedTime so that the full 381 | // transformation from 0 - 1 takes place in the 382 | // remaining time 383 | final float scaledTime = (interpolatedTime) 384 | / (1.0f - START_TRIM_DURATION_OFFSET); 385 | final float startTrim = startingTrim 386 | + ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR 387 | .getInterpolation(scaledTime)); 388 | ring.setStartTrim(startTrim); 389 | } 390 | 391 | // Moving the end trim starts after 50% of a single ring 392 | // animation completes 393 | if (interpolatedTime > END_TRIM_START_DELAY_OFFSET) { 394 | // scale the interpolatedTime so that the full 395 | // transformation from 0 - 1 takes place in the 396 | // remaining time 397 | final float minArc = MAX_PROGRESS_ARC - minProgressArc; 398 | float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET) 399 | / (1.0f - START_TRIM_DURATION_OFFSET); 400 | final float endTrim = startingEndTrim 401 | + (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime)); 402 | ring.setEndTrim(endTrim); 403 | } 404 | 405 | final float rotation = startingRotation + (0.25f * interpolatedTime); 406 | ring.setRotation(rotation); 407 | 408 | float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime) 409 | + (FULL_ROTATION * (mRotationCount / NUM_POINTS)); 410 | setRotation(groupRotation); 411 | } 412 | } 413 | }; 414 | animation.setRepeatCount(Animation.INFINITE); 415 | animation.setRepeatMode(Animation.RESTART); 416 | animation.setInterpolator(LINEAR_INTERPOLATOR); 417 | animation.setAnimationListener(new Animation.AnimationListener() { 418 | 419 | @Override 420 | public void onAnimationStart(Animation animation) { 421 | mRotationCount = 0; 422 | } 423 | 424 | @Override 425 | public void onAnimationEnd(Animation animation) { 426 | // do nothing 427 | } 428 | 429 | @Override 430 | public void onAnimationRepeat(Animation animation) { 431 | ring.storeOriginals(); 432 | ring.goToNextColor(); 433 | ring.setStartTrim(ring.getEndTrim()); 434 | if (mFinishing) { 435 | // finished closing the last ring from the swipe gesture; go 436 | // into progress mode 437 | mFinishing = false; 438 | animation.setDuration(ANIMATION_DURATION); 439 | ring.setShowArrow(false); 440 | } else { 441 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 442 | } 443 | } 444 | }); 445 | mAnimation = animation; 446 | } 447 | 448 | private final Callback mCallback = new Callback() { 449 | @Override 450 | public void invalidateDrawable(Drawable d) { 451 | invalidateSelf(); 452 | } 453 | 454 | @Override 455 | public void scheduleDrawable(Drawable d, Runnable what, long when) { 456 | scheduleSelf(what, when); 457 | } 458 | 459 | @Override 460 | public void unscheduleDrawable(Drawable d, Runnable what) { 461 | unscheduleSelf(what); 462 | } 463 | }; 464 | 465 | private static class Ring { 466 | private final RectF mTempBounds = new RectF(); 467 | private final Paint mPaint = new Paint(); 468 | private final Paint mArrowPaint = new Paint(); 469 | 470 | private final Callback mCallback; 471 | 472 | private float mStartTrim = 0.0f; 473 | private float mEndTrim = 0.0f; 474 | private float mRotation = 0.0f; 475 | private float mStrokeWidth = 5.0f; 476 | private float mStrokeInset = 2.5f; 477 | 478 | private int[] mColors; 479 | // mColorIndex represents the offset into the available mColors that the 480 | // progress circle should currently display. As the progress circle is 481 | // animating, the mColorIndex moves by one to the next available color. 482 | private int mColorIndex; 483 | private float mStartingStartTrim; 484 | private float mStartingEndTrim; 485 | private float mStartingRotation; 486 | private boolean mShowArrow; 487 | private Path mArrow; 488 | private float mArrowScale; 489 | private double mRingCenterRadius; 490 | private int mArrowWidth; 491 | private int mArrowHeight; 492 | private int mAlpha; 493 | private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 494 | private int mBackgroundColor; 495 | private int mCurrentColor; 496 | 497 | Ring(Callback callback) { 498 | mCallback = callback; 499 | 500 | mPaint.setStrokeCap(Paint.Cap.SQUARE); 501 | mPaint.setAntiAlias(true); 502 | mPaint.setStyle(Paint.Style.STROKE); 503 | 504 | mArrowPaint.setStyle(Paint.Style.FILL); 505 | mArrowPaint.setAntiAlias(true); 506 | } 507 | 508 | public void setBackgroundColor(int color) { 509 | mBackgroundColor = color; 510 | } 511 | 512 | /** 513 | * Set the dimensions of the arrowhead. 514 | * 515 | * @param width Width of the hypotenuse of the arrow head 516 | * @param height Height of the arrow point 517 | */ 518 | public void setArrowDimensions(float width, float height) { 519 | mArrowWidth = (int) width; 520 | mArrowHeight = (int) height; 521 | } 522 | 523 | /** 524 | * Draw the progress spinner 525 | */ 526 | public void draw(Canvas c, Rect bounds) { 527 | final RectF arcBounds = mTempBounds; 528 | arcBounds.set(bounds); 529 | arcBounds.inset(mStrokeInset, mStrokeInset); 530 | 531 | final float startAngle = (mStartTrim + mRotation) * 360; 532 | final float endAngle = (mEndTrim + mRotation) * 360; 533 | float sweepAngle = endAngle - startAngle; 534 | 535 | mPaint.setColor(mCurrentColor); 536 | c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); 537 | 538 | drawTriangle(c, startAngle, sweepAngle, bounds); 539 | 540 | if (mAlpha < 255) { 541 | mCirclePaint.setColor(mBackgroundColor); 542 | mCirclePaint.setAlpha(255 - mAlpha); 543 | c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2, 544 | mCirclePaint); 545 | } 546 | } 547 | 548 | private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) { 549 | if (mShowArrow) { 550 | if (mArrow == null) { 551 | mArrow = new Path(); 552 | mArrow.setFillType(Path.FillType.EVEN_ODD); 553 | } else { 554 | mArrow.reset(); 555 | } 556 | 557 | // Adjust the position of the triangle so that it is inset as 558 | // much as the arc, but also centered on the arc. 559 | float inset = (int) mStrokeInset / 2 * mArrowScale; 560 | float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX()); 561 | float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY()); 562 | 563 | // Update the path each time. This works around an issue in SKIA 564 | // where concatenating a rotation matrix to a scale matrix 565 | // ignored a starting negative rotation. This appears to have 566 | // been fixed as of API 21. 567 | mArrow.moveTo(0, 0); 568 | mArrow.lineTo(mArrowWidth * mArrowScale, 0); 569 | mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight 570 | * mArrowScale)); 571 | mArrow.offset(x - inset, y); 572 | mArrow.close(); 573 | // draw a triangle 574 | mArrowPaint.setColor(mCurrentColor); 575 | c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(), 576 | bounds.exactCenterY()); 577 | c.drawPath(mArrow, mArrowPaint); 578 | } 579 | } 580 | 581 | /** 582 | * Set the colors the progress spinner alternates between. 583 | * 584 | * @param colors Array of integers describing the colors. Must be non-null. 585 | */ 586 | public void setColors(@NonNull int[] colors) { 587 | mColors = colors; 588 | // if colors are reset, make sure to reset the color index as well 589 | setColorIndex(0); 590 | } 591 | 592 | /** 593 | * Set the absolute color of the progress spinner. This is should only 594 | * be used when animating between current and next color when the 595 | * spinner is rotating. 596 | * 597 | * @param color int describing the color. 598 | */ 599 | public void setColor(int color) { 600 | mCurrentColor = color; 601 | } 602 | 603 | /** 604 | * @param index Index into the color array of the color to display in 605 | * the progress spinner. 606 | */ 607 | public void setColorIndex(int index) { 608 | mColorIndex = index; 609 | mCurrentColor = mColors[mColorIndex]; 610 | } 611 | 612 | /** 613 | * @return int describing the next color the progress spinner should use when drawing. 614 | */ 615 | public int getNextColor() { 616 | return mColors[getNextColorIndex()]; 617 | } 618 | 619 | private int getNextColorIndex() { 620 | return (mColorIndex + 1) % (mColors.length); 621 | } 622 | 623 | /** 624 | * Proceed to the next available ring color. This will automatically 625 | * wrap back to the beginning of colors. 626 | */ 627 | public void goToNextColor() { 628 | setColorIndex(getNextColorIndex()); 629 | } 630 | 631 | public void setColorFilter(ColorFilter filter) { 632 | mPaint.setColorFilter(filter); 633 | invalidateSelf(); 634 | } 635 | 636 | /** 637 | * @param alpha Set the alpha of the progress spinner and associated arrowhead. 638 | */ 639 | public void setAlpha(int alpha) { 640 | mAlpha = alpha; 641 | } 642 | 643 | /** 644 | * @return Current alpha of the progress spinner and arrowhead. 645 | */ 646 | public int getAlpha() { 647 | return mAlpha; 648 | } 649 | 650 | /** 651 | * @param strokeWidth Set the stroke width of the progress spinner in pixels. 652 | */ 653 | public void setStrokeWidth(float strokeWidth) { 654 | mStrokeWidth = strokeWidth; 655 | mPaint.setStrokeWidth(strokeWidth); 656 | invalidateSelf(); 657 | } 658 | 659 | @SuppressWarnings("unused") 660 | public float getStrokeWidth() { 661 | return mStrokeWidth; 662 | } 663 | 664 | @SuppressWarnings("unused") 665 | public void setStartTrim(float startTrim) { 666 | mStartTrim = startTrim; 667 | invalidateSelf(); 668 | } 669 | 670 | @SuppressWarnings("unused") 671 | public float getStartTrim() { 672 | return mStartTrim; 673 | } 674 | 675 | public float getStartingStartTrim() { 676 | return mStartingStartTrim; 677 | } 678 | 679 | public float getStartingEndTrim() { 680 | return mStartingEndTrim; 681 | } 682 | 683 | public int getStartingColor() { 684 | return mColors[mColorIndex]; 685 | } 686 | 687 | @SuppressWarnings("unused") 688 | public void setEndTrim(float endTrim) { 689 | mEndTrim = endTrim; 690 | invalidateSelf(); 691 | } 692 | 693 | @SuppressWarnings("unused") 694 | public float getEndTrim() { 695 | return mEndTrim; 696 | } 697 | 698 | @SuppressWarnings("unused") 699 | public void setRotation(float rotation) { 700 | mRotation = rotation; 701 | invalidateSelf(); 702 | } 703 | 704 | @SuppressWarnings("unused") 705 | public float getRotation() { 706 | return mRotation; 707 | } 708 | 709 | public void setInsets(int width, int height) { 710 | final float minEdge = (float) Math.min(width, height); 711 | float insets; 712 | if (mRingCenterRadius <= 0 || minEdge < 0) { 713 | insets = (float) Math.ceil(mStrokeWidth / 2.0f); 714 | } else { 715 | insets = (float) (minEdge / 2.0f - mRingCenterRadius); 716 | } 717 | mStrokeInset = insets; 718 | } 719 | 720 | @SuppressWarnings("unused") 721 | public float getInsets() { 722 | return mStrokeInset; 723 | } 724 | 725 | /** 726 | * @param centerRadius Inner radius in px of the circle the progress 727 | * spinner arc traces. 728 | */ 729 | public void setCenterRadius(double centerRadius) { 730 | mRingCenterRadius = centerRadius; 731 | } 732 | 733 | public double getCenterRadius() { 734 | return mRingCenterRadius; 735 | } 736 | 737 | /** 738 | * @param show Set to true to show the arrow head on the progress spinner. 739 | */ 740 | public void setShowArrow(boolean show) { 741 | if (mShowArrow != show) { 742 | mShowArrow = show; 743 | invalidateSelf(); 744 | } 745 | } 746 | 747 | /** 748 | * @param scale Set the scale of the arrowhead for the spinner. 749 | */ 750 | public void setArrowScale(float scale) { 751 | if (scale != mArrowScale) { 752 | mArrowScale = scale; 753 | invalidateSelf(); 754 | } 755 | } 756 | 757 | /** 758 | * @return The amount the progress spinner is currently rotated, between [0..1]. 759 | */ 760 | public float getStartingRotation() { 761 | return mStartingRotation; 762 | } 763 | 764 | /** 765 | * If the start / end trim are offset to begin with, store them so that 766 | * animation starts from that offset. 767 | */ 768 | public void storeOriginals() { 769 | mStartingStartTrim = mStartTrim; 770 | mStartingEndTrim = mEndTrim; 771 | mStartingRotation = mRotation; 772 | } 773 | 774 | /** 775 | * Reset the progress spinner to default rotation, start and end angles. 776 | */ 777 | public void resetOriginals() { 778 | mStartingStartTrim = 0; 779 | mStartingEndTrim = 0; 780 | mStartingRotation = 0; 781 | setStartTrim(0); 782 | setEndTrim(0); 783 | setRotation(0); 784 | } 785 | 786 | private void invalidateSelf() { 787 | mCallback.invalidateDrawable(null); 788 | } 789 | } 790 | } 791 | -------------------------------------------------------------------------------- /widgetpro/src/main/java/gzoomswiperefresh/TabFragmentAdapter.java: -------------------------------------------------------------------------------- 1 | package gzoomswiperefresh; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentStatePagerAdapter; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 11 | * Created by Dimon on 2016/3/23. 12 | * 因为是抽象类所以要继承 13 | */ 14 | public class TabFragmentAdapter extends FragmentStatePagerAdapter { 15 | 16 | private List mFragments; 17 | private List mTitles; 18 | 19 | public TabFragmentAdapter(FragmentManager fm, List fragments, List titles) { 20 | super(fm); 21 | mFragments = fragments; 22 | mTitles = titles; 23 | } 24 | 25 | @Override 26 | public Fragment getItem(int position) { 27 | return mFragments.get(position); 28 | } 29 | 30 | @Override 31 | public int getCount() { 32 | return mFragments.size(); 33 | } 34 | 35 | @Override 36 | public CharSequence getPageTitle(int position) { 37 | return mTitles.get(position); 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /widgetpro/src/main/java/gzoomswiperefresh/TempAdapter.java: -------------------------------------------------------------------------------- 1 | package gzoomswiperefresh; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.util.Log; 6 | import android.view.Gravity; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | 13 | import com.example.widgetpro.R; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * Created by gzoom on 2016/11/14. 19 | */ 20 | public class TempAdapter extends RecyclerView.Adapter{ 21 | 22 | LayoutInflater layoutInflater; 23 | Listdata; 24 | 25 | public TempAdapter(Context c, Listlist) 26 | { 27 | layoutInflater=LayoutInflater.from(c); 28 | data=list; 29 | } 30 | 31 | 32 | @Override 33 | public holder onCreateViewHolder(ViewGroup parent, int viewType) { 34 | View view =layoutInflater.inflate(R.layout.temp_item,parent,false); 35 | return new holder(view); 36 | } 37 | 38 | @Override 39 | public void onBindViewHolder(holder holder, int position) { 40 | holder.textView.setText(data.get(position)); 41 | } 42 | 43 | @Override 44 | public int getItemCount() { 45 | Log.e("fish","size=="+data.size()); 46 | return data.size(); 47 | } 48 | 49 | class holder extends RecyclerView.ViewHolder{ 50 | 51 | TextView textView; 52 | public holder(View itemView) { 53 | super(itemView); 54 | textView= (TextView) itemView.findViewById(R.id.temp_textview); 55 | textView.setGravity(Gravity.CENTER); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/drawable/ic_favorite_border_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/drawable/ic_slide_menu_avatar_no_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SGZoom/DailyWidget/d5023c52639eb1c64e630ace6e12298e930ae30f/widgetpro/src/main/res/drawable/ic_slide_menu_avatar_no_login.png -------------------------------------------------------------------------------- /widgetpro/src/main/res/drawable/start.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SGZoom/DailyWidget/d5023c52639eb1c64e630ace6e12298e930ae30f/widgetpro/src/main/res/drawable/start.jpg -------------------------------------------------------------------------------- /widgetpro/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/layout/app_bar_gzoom.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 21 | 27 | 28 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/layout/app_bar_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/layout/nav_header_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 22 | 23 | 31 | 32 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/layout/own_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/layout/temp_fag.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/layout/temp_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 13 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/menu/drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SGZoom/DailyWidget/d5023c52639eb1c64e630ace6e12298e930ae30f/widgetpro/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /widgetpro/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SGZoom/DailyWidget/d5023c52639eb1c64e630ace6e12298e930ae30f/widgetpro/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /widgetpro/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SGZoom/DailyWidget/d5023c52639eb1c64e630ace6e12298e930ae30f/widgetpro/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /widgetpro/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SGZoom/DailyWidget/d5023c52639eb1c64e630ace6e12298e930ae30f/widgetpro/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /widgetpro/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SGZoom/DailyWidget/d5023c52639eb1c64e630ace6e12298e930ae30f/widgetpro/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /widgetpro/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #8a8893 5 | #525359 6 | #a14d18 7 | #6c1b36 8 | #ffffff 9 | #e71b1b 10 | #FFF 11 | #53a4fc 12 | 13 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 100dp 6 | 16dp 7 | 160dp 8 | 16dp 9 | 10 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | WidgetPro 3 | Gzoom的APP 4 | Main2Activity 5 | 6 | Open navigation drawer 7 | Close navigation drawer 8 | 分享 9 | Settings 10 | 11 | -------------------------------------------------------------------------------- /widgetpro/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /widgetpro/src/test/java/com/example/widgetpro/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.example.widgetpro; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } --------------------------------------------------------------------------------