English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Android ListView Implementation for Pull-down Top Image Enlargement Effect

This article shares the specific code for the Android ListView pull-down top image enlargement with everyone for reference. The specific content is as follows

When checking the code of the master on git, I found that it is decompiled from others' code without comments, and the code is not completely compiled. Therefore, I have added simple comments here for learning purposes only.

Variable Description

The variable includes: custom return animation acceleration, custom animation thread, header image view, the final y coordinate, the prepared ratio, the enlarged ratio, and so on.

private static final String TAG = "PullToZoomListView";
 private static final int INVALID_VALUE = -1;//Reset value
 //Custom acceleration animation
 private static final Interpolator sInterpolator = new Interpolator() {
  public float getInterpolation(float interpolator) {
   float f = interpolator - 1.0F;
   return 1.0F + f * (f * (f * (f * f)));
  }
 };
 private int mActivePointerId = INVALID_VALUE;//Current finger ID
 private FrameLayout mHeaderContainer;//Header
 private int mHeaderHeight;//Header image height
 private ImageView mHeaderImage;//Header image
 float mLastMotionY = INVALID_VALUE;//Final y coordinate
 float mLastScale = INVALID_VALUE;//Final scale
 float mMaxScale = INVALID_VALUE;//Maximum scale
 private OnScrollListener mOnScrollListener;//Scroll listener
 private ScalingRunnalable mScalingRunnalable;//Animation thread
 private int mScreenHeight;//Screen height
 private ImageView mShadow;//Shadow mask

Custom View initialization: Sets the header and mask and sets the listener.

/**
  * Initialization
  * @param paramContext
  */
 private void init(Context paramContext) {
  DisplayMetrics metrics = new DisplayMetrics();
  ((Activity) paramContext).getWindowManager().getDefaultDisplay().getMetrics(metrics);
  this.mScreenHeight = metrics.heightPixels;//Assign screen height
  this.mHeaderContainer = new FrameLayout(paramContext);//Header
  this.mHeaderImage = new ImageView(paramContext);//Header image
  int screenWidth = metrics.widthPixels;//Screen width
  //Set the style of the header view Set screen width, the maximum style height is the height of the screen9/16
  setHeaderViewSize(screenWidth, (int) (9.0F * (screenWidth / 16.0F)));
  this.mShadow = new ImageView(paramContext);//Mask
  FrameLayout.LayoutParams layoutParams =
    new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
      ViewGroup.LayoutParams.MATCH_PARENT);
  layoutParams.gravity = Gravity.CENTER;
  this.mShadow.setLayoutParams(layoutParams);//Set mask style
  //Add View to header
  this.mHeaderContainer.addView(this.mHeaderImage);
  this.mHeaderContainer.addView(this.mShadow);
  //Add header
  addHeaderView(this.mHeaderContainer);
  //Initialize return animation
  this.mScalingRunnalable = new ScalingRunnalable();
  //Set listener
  super.setOnScrollListener(this);
 }

Start animation: Determine the bottom position of the current header layout - whether it is greater than the initial height of the image.

/**
  * Start animation
  */
 private void endScraling() {
  if (this.mHeaderContainer.getBottom() >= this.mHeaderHeight) {
   Log.d(TAG, "this.mScalingRunnalable.startAnimation("200L)")
   this.mScalingRunnalable.startAnimation(200L);
  }
 }

Assign the first finger when multiple fingers touch.

/**
  * When multiple touches occur, press down, when the first finger is lifted, and then another finger is pressed down, the finger pointer of the pressed event is set as the current finger pointer
  *
  * @param motionEvent
  */
 private void onSecondaryPointerUp(MotionEvent motionEvent) {
  Log.d(TAG, "onSecondaryPointerUp motionEvent.getPointerId(0) = ") + motionEvent.getPointerId(0));
  Log.d(TAG, "onSecondaryPointerUp this.mActivePointerId = ") + this.mActivePointerId);
  if (motionEvent.getPointerId(0) == this.mActivePointerId) {
   this.mLastMotionY = motionEvent.getY(0);
   this.mActivePointerId = motionEvent.getPointerId(0);
  }
  Log.d(TAG, "onSecondaryPointerUp mLastMotionY = ") + mLastMotionY);
  Log.d(TAG, "onSecondaryPointerUp mActivePointerId = ") + mActivePointerId);
 }

Reset all the data

/**
 * Reset all data
*/
 private void reset() {
  this.mActivePointerId = INVALID_VALUE;
  this.mLastMotionY = INVALID_VALUE;
  this.mMaxScale = INVALID_VALUE;
  this.mLastScale = INVALID_VALUE;
 }

Modify the layout style when scrolling up

@Override
 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  Log.d(TAG, "onScroll");
  float bottomSpacing = this.mHeaderHeight - this.mHeaderContainer.getBottom();
  Log.d(TAG, "onScroll bottomSpacing = " + bottomSpacing);
  if ((bottomSpacing > 0.0F) && (bottomSpacing < this.mHeaderHeight)) {//If it is a upward scroll
   int toUpScroll = (int) (0.65D * bottomSpacing);
   this.mHeaderImage.scrollTo(0, -toUpScroll);
   Log.d(TAG, "onScroll Upward scrolling toUpScroll = " + toUpScroll);
  } else if (this.mHeaderImage.getScrollY() != 0) {
   Log.d(TAG, "onScroll this.mHeaderImage.getScrollY() = " + this.mHeaderImage.getScrollY());
   this.mHeaderImage.scrollTo(0, 0);
  }
  if (this.mOnScrollListener != null) {
   this.mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
  }
 }

Handle different events and modify the layout style

@Override
 public boolean onTouchEvent(MotionEvent motionEvent) {
  switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
   case MotionEvent.ACTION_OUTSIDE:
   case MotionEvent.ACTION_DOWN:
    if (!this.mScalingRunnalable.mIsFinished) {
     this.mScalingRunnalable.abortAnimation();
    }
    this.mLastMotionY = motionEvent.getY();
    //Obtain the ID of the first finger pointer
    this.mActivePointerId = motionEvent.getPointerId(0);
    this.mMaxScale = (this.mScreenHeight / this.mHeaderHeight);
    this.mLastScale = (this.mHeaderContainer.getBottom() / this.mHeaderHeight);
    Log.d(TAG, "onTouchEvent ACTION_DOWN mLastMotionY = " + mLastMotionY);
    Log.d(TAG, "onTouchEvent ACTION_DOWN mActivePointerId = " + mActivePointerId);
    Log.d(TAG, "onTouchEvent ACTION_DOWN mMaxScale = " + mMaxScale);
    Log.d(TAG, "onTouchEvent ACTION_DOWN mLastScale = " + mLastScale);
    break;
   case MotionEvent.ACTION_MOVE:
    Log.d(TAG, "onTouchEvent ACTION_MOVE mActivePointerId" + mActivePointerId);
    //Get the current pointer of the phone with the specified id
    int pointer = motionEvent.findPointerIndex(this.mActivePointerId);
    //Check if the pointer is not null
    if (pointer == INVALID_VALUE) {
     Log.e(TAG, "Invalid pointerId=" + this.mActivePointerId + " in onTouchEvent");
    } else {
     //If no value is assigned at the beginning, it needs to be assigned
     if (this.mLastMotionY == INVALID_VALUE) {
      this.mLastMotionY = motionEvent.getY(pointer);
     }
     if (this.mHeaderContainer.getBottom() >= this.mHeaderHeight) {
      //Get header style
      ViewGroup.LayoutParams headerParams = this.mHeaderContainer.getLayoutParams();
      float currentScale = ((motionEvent.getY(pointer) - this.mLastMotionY + this.mHeaderContainer.getBottom())
        / this.mHeaderHeight - this.mLastScale)
        / 2.0F + this.mLastScale;
      if ((this.mLastScale <= 1.0D) && (currentScale < this.mLastScale)) {
       //If the final ratio is less than the default and the current ratio is less than the last ratio, then modify the height of the header
       headerParams.height = this.mHeaderHeight;
       this.mHeaderContainer.setLayoutParams(headerParams);
       return super.onTouchEvent(motionEvent);
      } else {
       //Otherwise, assign the current ratio to the last ratio
       this.mLastScale = Math.min(Math.max(currentScale, 1.0F), this.mMaxScale);
       headerParams.height = ((int) (this.mHeaderHeight * this.mLastScale));
       //Determine if the modified height is less than the screen height
       if (headerParams.height < this.mScreenHeight) {
        this.mHeaderContainer.setLayoutParams(headerParams);
       }
       //Record the last y-coordinate
       this.mLastMotionY = motionEvent.getY(pointer);
       return true;
      }
     }
     this.mLastMotionY = motionEvent.getY(pointer);
    }
    break;
   case MotionEvent.ACTION_UP:
    Log.d(TAG, "onTouchEvent ACTION_UP Reset");
    //Reset
    reset();
    //When the finger lifts, settle the stretch and judge whether the animation is enabled
    endScraling();
    break;
   case MotionEvent.ACTION_CANCEL:
    int actionIndex = motionEvent.getActionIndex();//Get the current topmost pointer
    this.mLastMotionY = motionEvent.getY(actionIndex);//Get the last y-coordinate
    this.mActivePointerId = motionEvent.getPointerId(actionIndex);//Get the finger of the topmost pointer
    Log.d(TAG, "onTouchEvent ACTION_CANCEL actionIndex = ") + actionIndex + " mLastMotionY = " + mLastMotionY + " mActivePointerId = " + mActivePointerId);
    break;
   case MotionEvent.ACTION_POINTER_DOWN:
    //When the second finger is pressed or released, trigger this event
    onSecondaryPointerUp(motionEvent);
    this.mLastMotionY = motionEvent.getY(motionEvent.findPointerIndex(this.mActivePointerId));
    Log.d(TAG, "onTouchEvent_Po ACTION_POINTER_DOWN mLastMotionY = " + mLastMotionY);
    break;
   case MotionEvent.ACTION_POINTER_UP:
    //When the second finger is pressed or released
    Log.d(TAG, "onTouchEvent_Po ACTION_POINTER_UP ");
    break;
  }
  return super.onTouchEvent(motionEvent);
 }

Animation when returning upwards

/**
  * Animation returning upwards
  */
 class ScalingRunnalable implements Runnable {
  long mDuration;//Duration
  boolean mIsFinished = true;//Whether to end
  float mScale;//Ratio
  long mStartTime;//Start time
  ScalingRunnalable() {
  }
  /**
   * Stop animation
   */
  public void abortAnimation() {
   this.mIsFinished = true;
  }
  /**
   * Whether to stop
   *
   * @return
   */
  public boolean isFinished() {
   return this.mIsFinished;
  }
  public void run() {
   Log.d(TAG, "ScalingRunnalable mIsFinished = " + this.mIsFinished + " this.mScale = " + this.mScale);
   float currentScale;
   ViewGroup.LayoutParams mHeaderContainerParams;//Header style
   //Determine whether to stop and if it has slid over the default size
   if ((!this.mIsFinished) && (this.mScale > 1.0D)) {
    float currentTime = ((float) SystemClock.currentThreadTimeMillis() - (float) this.mStartTime) / (float) this.mDuration;
    currentScale = this.mScale - (this.mScale - 1.0F) * PullToZoomListView.sInterpolator.getInterpolation(currentTime);
    Log.d(TAG, "ScalingRunnalable currentTime = " + currentTime + " currentScale = " + currentScale);
    mHeaderContainerParams = PullToZoomListView.this.mHeaderContainer.getLayoutParams();
    if (currentScale > 1.0F) {
     Log.d(TAG, "ScalingRunnalable currentScale > 1.0 -- 修改头部高度);
     mHeaderContainerParams.height = PullToZoomListView.this.mHeaderHeight;
     mHeaderContainerParams.height = ((int) (currentScale * PullToZoomListView.this.mHeaderHeight));
     PullToZoomListView.this.mHeaderContainer.setLayoutParams(mHeaderContainerParams);
     PullToZoomListView.this.post(this);//循环执行
    } else {
     Log.d(TAG, "ScalingRunnalable currentScale < 1.0 -- 中止);
     this.mIsFinished = true;
    }
   }
  }
  public void startAnimation(long paramLong) {
   Log.d(TAG, "ScalingRunnalable 开始执行动画");
   this.mStartTime = SystemClock.currentThreadTimeMillis();
   this.mDuration = paramLong;
   this.mScale = ((float) (PullToZoomListView.this.mHeaderContainer.getBottom())); / PullToZoomListView.this.mHeaderHeight);
   this.mIsFinished = false;
   Log.d(TAG, "ScalingRunnalable this.mStartTime = "); + this.mStartTime);
   Log.d(TAG, "ScalingRunnalable this.mDuration = "); + this.mDuration);
   Log.d(TAG, "ScalingRunnalable this.mScale = "); + this.mScale);
   Log.d(TAG, "ScalingRunnalable this.mIsFinished = "); + this.mIsFinished);
   PullToZoomListView.this.post(this);
  }
 }

That's all for this article. I hope it will be helpful to everyone's learning and I also hope everyone will support the Naya Tutorial.

Statement: The content of this article is from the Internet, and the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously. This website does not own the copyright, has not been edited by humans, and does not assume any relevant legal liability. If you find any content suspected of copyright infringement, please send an email to: notice#oldtoolbag.com (Please replace # with @ when sending an email to report, and provide relevant evidence. Once verified, this site will immediately delete the infringing content.)

You May Also Like