Android custom control-auto-like home pull-down refresh
About the implementation principle of pull-down refresh I have introduced in detail in the previous article about the Android custom control imitation Meituan pull-down refresh. This article mainly introduces the implementation principle of Dial animation.
The pull-down refresh status of the car House is divided into three states:
The first state is the pull-down refresh status (pull to refresh). In this state, a dial dynamically changes the pointer angle with the pull-down distance.
The second state is release to refresh, which is an animation of pointer angle changes.
Implementation of the first State:
We use a custom View to achieve this effect. We get the two images used for the pull-down refresh from the apk of the car house:
We draw the first image on the canvas as the background, then rotate the canvas dynamically based on the current progress value, and then draw the second chapter on the canvas, we can see that the table needle is actually rotated by the canvas.
@ Override protected void onDraw (Canvas canvas) {super. onDraw (canvas); // draw the first image on the canvas. drawBitmap (finalBackGroundBitmap, 0, 0, null); // rotate the canvas. rotate (mCurrentProgress * 2.7f, x/2, y/2); // draw the second image on the rotated canvas. drawBitmap (finalPointerBitmap, 0, 0, null );}
The complete code of the custom View is as follows:
/** * Created by zhangqi on 15/10/17. */public class AutoHome extends View{ private Bitmap backGroundBitmap; public Bitmap pointerBitmap; private int x; private int y; private Bitmap finalBackGroundBitmap; private Bitmap finalPointerBitmap; private float mCurrentProgress; public AutoHome(Context context) { super(context); init(context); } public AutoHome(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public AutoHome(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { backGroundBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(context.getResources(), R.drawable.load_icon_dial2x)); pointerBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(context.getResources(), R.drawable.load_icon_pointer2x)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec),measureWidth(heightMeasureSpec)); x = getMeasuredWidth(); y = getMeasuredHeight(); finalBackGroundBitmap = Bitmap.createScaledBitmap(backGroundBitmap, x, y, false); finalPointerBitmap = Bitmap.createScaledBitmap(pointerBitmap, x, y, false); } private int measureWidth(int widMeasureSpec){ int result = 0; int size = MeasureSpec.getSize(widMeasureSpec); int mode = MeasureSpec.getMode(widMeasureSpec); if (mode == MeasureSpec.EXACTLY){ result = size; }else{ result = backGroundBitmap.getWidth(); if (mode == MeasureSpec.AT_MOST){ result = Math.min(result,size); } } return result; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(finalBackGroundBitmap,0,0,null); canvas.rotate(mCurrentProgress*2.7f,x/2,y/2); canvas.drawBitmap(finalPointerBitmap, 0, 0, null); } public void setCurrentProgress(float progress){ mCurrentProgress = progress*100; }}
Then we use SeekBar in the Activity to simulate a progress value and pass it to our custom View.
public class MainActivity extends AppCompatActivity { private SeekBar mSeekBar; private AutoHome mAutoHome; private float mCurrentProgress; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSeekBar = (SeekBar) findViewById(R.id.seekbar); mAutoHome = (AutoHome) findViewById(R.id.autohome); mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { mCurrentProgress = (float)seekBar.getProgress()/(float)seekBar.getMax(); mAutoHome.setCurrentProgress(mCurrentProgress); mAutoHome.invalidate(); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); }
Implementation of the second State:
The second state is that the table needle is executing a rotating animation. We can write the table needle as a custom View, then the dial is used as the background image, and then the table needle View is used to execute the rotate animation.
/*** Created by zhangqi on 15/10/27. */public class PointerView extends View {private int x; private int y; private Bitmap finalPointerBitmap; private Bitmap pointerBitmap; public PointerView (Context context) {super (context ); init ();} public PointerView (Context context, AttributeSet attrs) {super (context, attrs); init () ;}public PointerView (Context context, AttributeSet attrs, int defStyleAtt R) {super (context, attrs, defStyleAttr); init ();} private void init () {pointerBitmap = Bitmap. createBitmap (BitmapFactory. decodeResource (getResources (), R. drawable. extends) ;}@ Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {measures (measureWidth (widthMeasureSpec), measureWidth (Measures); x = measures (); y = getMeasuredHe Ight (); finalPointerBitmap = Bitmap. createScaledBitmap (pointerBitmap, x, y, false);} private int measureWidth (int widMeasureSpec) {int result = 0; int size = MeasureSpec. getSize (widMeasureSpec); int mode = MeasureSpec. getMode (widMeasureSpec); if (mode = MeasureSpec. EXACTLY) {result = size;} else {result = pointerBitmap. getWidth (); if (mode = MeasureSpec. AT_MOST) {result = Math. min (result, size );} Return result;} @ Override protected void onDraw (Canvas canvas) {super. onDraw (canvas); // the initial position of the table needle is 270 degrees! Canvas. rotate (270, x/2, y/2); canvas. drawBitmap (finalPointerBitmap, null );}}
Then we write in the xml file as follows:
<framelayout android:id="@+id/anim_container" android:layout_height="45dp" android:layout_margin="15dp" android:layout_width="45dp" android:visibility="gone">
</framelayout>
In this way, the dial is used as the background. We can operate the table needle to execute the rotate animation.
MAutoHomeAnim = (PointerView) headerView. findViewById (R. id. anim_pointer); animation = AnimationUtils. loadAnimation (context, R. anim. pointer_rotate); // execute the animation mAutoHomeAnim. startAnimation (animation );
In listview
Because the pull-down refresh core code is the same as the pull-down refresh of the US group, here I only extract different parts
Private void changeHeaderByState (int state) {switch (state) {case DONE: headerView. setPadding (0,-headerViewHeight, 0, 0); // The first view displays mAutoHome. setVisibility (View. VISIBLE); // stop the animation mAutoHomeAnim of the second-stage view. clearAnimation (); // hides the second-stage view from mAnimContainer. setVisibility (View. GONE); break; case RELEASE_TO_REFRESH: Unlock (refresh); break; case PULL_TO_REFRESH: TV _pull_to_refresh.setText (pull-down refresh); // mAutoHome is displayed in the first view. setVisibility (View. VISIBLE); // stop the second-stage animation mAutoHomeAnim. clearAnimation (); // hides mAnimContainer from the second-stage view. setVisibility (View. GONE); break; case REFRESHING: TV _pull_to_refresh.setText (REFRESHING); // hides mAutoHome from the first-stage view. setVisibility (View. GONE); // display the second-stage view in mAnimContainer. setVisibility (View. VISIBLE); // stop the second-stage animation mAutoHomeAnim. clearAnimation (); // start the second-stage animation mAutoHomeAnim. startAnimation (animation); break; default: break ;}}