Android | Custom pull up drawer + combined animation effect

Author: I Wang a

Not much to say, let's take a look at the renderings

The main function achieved is to pull up the drawer (solving the sliding conflict of the subview) + edge animation + middle ball and seekbar effect animation. The yellow part is the whole upper drawer, and the green part is the horizontal recyclerview. A friend said that the damping effect is perfect... Because the renderings have no damping effect, so I didn’t study it...!

First summarize the main technologies used

  • ScrollView + NestedScrollingParent + NestedScrollingChild (mainly pull up the drawer to resolve internal and external sliding conflicts)
  • Custom view, Bezier curve, lineTo, drawCircle, drawPath and other commonly used
    emmmm seems to be gone, in fact, it is mainly to customize view drawing, and it is not very complicated.

You can also put a picture on the top, like sauce purple

You can also put pictures and text in the middle of the circle. When you slide up and down, the internal pictures and text will also change. In fact, the principle is the same. You can put anything after a meeting, which will be introduced later in the article.
The effect is sauce purple

I put LinearLayout in the drawer, and then dynamically added multiple RecyclerViews that can be scrolled horizontally, sliding up and down, sliding left, sliding right, easy and stress-free~~ That’s so exciting

The effect is finished, let’s take a look at how to achieve it

1. Upward sliding drawer + internal scrolling of drawers to resolve conflicts between up and down scrolling

  1. 1. First of all, you have to understand NestedScrollingParent & NestedScrollingChild,
    which is mainly the monitoring of scrolling of parent view and child view and the transmission of scrolling signals between each other.
  2. 2. Sort out the scrolling requirements:
    slide up,
    scroll the parent view -> after listening to the top -> scroll the child view,
    slide down,
    first scroll the child view -> after the child view to the top -> scroll the parent view
  3. 3. The overall layout
    There are three child layouts in the parent layout
// 父布局的滚动
<com.yoyo.topscrollview.scrollview.ScrollParentView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true"
        android:orientation="vertical"
        android:id="@+id/scrollParentView">
        //需要上滑隐藏的部分
        <RelativeLayout
            android:id="@+id/rl_transparentTop"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
        //上滑到顶需要吸附的部分
        <RelativeLayout
            android:id="@+id/center"
            android:layout_width="match_parent"
            android:layout_height="100dp">
            <com.yoyo.topscrollview.centerview.WaveView
                android:id="@+id/waveView"
                android:layout_centerInParent="true"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
            <com.yoyo.topscrollview.centerview.CircleView
                android:id="@+id/circleView"
                android:layout_centerInParent="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:ring_color="@color/lightPink"
                app:circle_color="@color/pink"/>
        </RelativeLayout>

        //子布局 内层滑动部分
        <com.yoyo.topscrollview.scrollview.ScrollChildView
            android:id="@+id/scrollChildView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:scrollbars="none"
            android:overScrollMode="never">
            <LinearLayout
                android:id="@+id/ll_content"
                android:background="@color/orange"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:paddingLeft="15dp"
                android:paddingRight="15dp">

            </LinearLayout>
        </com.yoyo.topscrollview.scrollview.ScrollChildView>
    </com.yoyo.topscrollview.scrollview.ScrollParentView>

In the current demo

  • Swipe up hidden part: transparent top
  • Sliding up to the top part: the arc and circle in the middle
  1. 4、ScrollParentView
  • Whether onStartNestedScroll accepts nested scrolling, only if it returns true, other methods will be called
  • onNestedPreScroll is called before the inner view handles the scroll event, which allows the outer view to consume part of the scroll first
  • onNestedScroll is called after the inner view consumes the remaining scroll, and the last remaining scroll can be processed here
  • onNestedPreFling is called before the Fling event processing of the inner view
  • onNestedFling is called after the Fling event of the inner view is processed
    private View topView ;
    private View centerView;
    private View contentView;
    private NestedScrollingParentHelper mParentHelper;
    private int imgHeight;
    private int tvHeight;

    public ScrollParentView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ScrollParentView(Context context) {
        super(context);
        init();
    }

    /**
     * 初始化内部三个子视图
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        topView = getChildAt(0);
        centerView =  getChildAt(1);
        contentView = getChildAt(2);
        topView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if(imgHeight<=0){
                    imgHeight =  topView.getMeasuredHeight();
                }
            }
        });
        centerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if(tvHeight<=0){
                    tvHeight =  centerView.getMeasuredHeight();
                }
            }
        });

    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(getMeasuredWidth(), topView.getMeasuredHeight() + centerView.getMeasuredHeight() + contentView.getMeasuredHeight());

    }
    public int  getTopViewHeight(){
        return topView.getMeasuredHeight();
    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {

        return true;
    }
    private void init() {
        mParentHelper = new NestedScrollingParentHelper(this);

    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
        mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
    }

    @Override
    public void onStopNestedScroll(View target) {
        mParentHelper.onStopNestedScroll(target);
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
    }

    /**
     * 处理上滑和下滑 顶部需要滚动的距离
     * @param target
     * @param dx
     * @param dy
     * @param consumed
     */
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        boolean headerScrollUp = dy > 0 && getScrollY() < imgHeight;
        boolean headerScrollDown = dy < 0 && getScrollY() > 0 && !target.canScrollVertically(-1);
        if (headerScrollUp || headerScrollDown) {
            scrollBy(0, dy);
            consumed[1] = dy;
        }
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        return false;
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        return false;
    }

    @Override
    public int getNestedScrollAxes() {
        return 0;
    }

    @Override
    public void scrollTo(int x, int y) {
        if(y<0){
            y=0;
        }
        if(y>imgHeight){
            y=imgHeight;
        }

        super.scrollTo(x, y);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(event.getAction()==MotionEvent.ACTION_DOWN){
            return true;
        }
        return super.onTouchEvent(event);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return super.onInterceptTouchEvent(event);
    }
  1. 5.
    The scrolling of the ScrollChildView child layout is relatively simple, mainly through proxy processing and some scroll events of the parent layout
 private NestedScrollingChildHelper mScrollingChildHelper;

    public ScrollChildView(Context context) {
        super(context);
        init(context);
    }

    public ScrollChildView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ScrollChildView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public void init(Context context) {
        final ViewConfiguration configuration = ViewConfiguration.get(context);

    }

    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        getScrollingChildHelper().setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return getScrollingChildHelper().isNestedScrollingEnabled();
    }

    @Override
    public boolean startNestedScroll(int axes) {

        boolean bl = getScrollingChildHelper().startNestedScroll(axes);
        return bl;
    }

    @Override
    public void stopNestedScroll() {
        getScrollingChildHelper().stopNestedScroll();
    }

    @Override
    public boolean hasNestedScrollingParent() {
        return getScrollingChildHelper().hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
                                        int dyUnconsumed, int[] offsetInWindow) {
        return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed,
                dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
    }

    private NestedScrollingChildHelper getScrollingChildHelper() {
        if (mScrollingChildHelper == null) {
            mScrollingChildHelper = new NestedScrollingChildHelper(this);
            mScrollingChildHelper.setNestedScrollingEnabled(true);
        }
        return mScrollingChildHelper;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(getMeasuredWidth(),getMeasuredHeight()+((ScrollParentView)getParent()).getTopViewHeight());
    }

Here you can achieve the same scrolling effect as the rendering

2. Animation similar to water ripples

It’s more intuitive to see
this is a simple effect drawn with Bezier curves

  • First -> Understand the Bezier curve
    . Many people have written detailed articles about the Bezier curve. Learn about it. I won't introduce it in detail here.

I used two third-order Bezier curves here, separated from the middle, one on the left and one on the right. Then, the view is divided into half up and down, the middle point remains unchanged, the height on both sides increases, and the two sides are fan-shaped rounded corners. , And then draw the lineto into a closed figure, so that the animation effect shown above appears.

Exploded view

@Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        mPath.reset();
        // start point
        mPath.moveTo(mStartX, mViewHeightHalf);
        // 贝塞尔曲线
        mPath.rCubicTo(mViewWidthHalf / 4, 0, mViewWidthHalf / 4, Math.abs(mViewHeightHalf - mCenterRadius), mViewWidthHalf / 2, Math.abs(mViewHeightHalf - mCenterRadius));
        mPath.rCubicTo(mViewWidthHalf / 4, 0, mViewWidthHalf / 4, -Math.abs(mViewHeightHalf - mCenterRadius), mViewWidthHalf / 2, -Math.abs(mViewHeightHalf - mCenterRadius));

        // 两边的圆角扇形
        mPath.addArc(0, mViewHeightHalf, 200, mViewHeightHalf + 200, 180, 90);
        mPath.addArc(mViewWidthHalf * 2 - 200, mViewHeightHalf, mViewWidthHalf * 2, mViewHeightHalf + 200, 270, 90);

        // 图形边框
        mPath.lineTo(this.getMeasuredWidth() - 100, mViewHeightHalf);
        mPath.lineTo(this.getMeasuredWidth(), mViewHeightHalf + 100);
        mPath.lineTo(this.getMeasuredWidth(), this.getMeasuredHeight());

        mPath.lineTo(0, this.getMeasuredHeight());
        mPath.lineTo(0, mViewHeightHalf + 100);
        mPath.lineTo(100, mViewHeightHalf);
        mPath.lineTo(mStartX, mViewHeightHalf);
        mPath.lineTo(mStartX * 2 + mStartX, mViewHeightHalf);

        mPath.setFillType(Path.FillType.WINDING);
        //Close path
        mPath.close();
        canvas.drawPath(mPath, mCenterLinePaint);

    }

Three, circle and ring

Everyone should be familiar with this part. Custom views are often used, so I won’t say much about the usage. Record the zooming and transparency changes of the middle image.

Bitmap.createScaledBitmap will construct a new bitmap according to a certain ratio (size) of a currently existing bitmap paint.setAlpha(mAlpha); Set the transparency of the brush

Then continuously change the radius of the circle and the ring, the size of the picture, and the transparency of the brush in the animation to achieve the effect

Fourth, the overall sliding effect

The changes in the arc, circle, ring and picture of the drawer are mainly calculated by monitoring the current upward sliding distance and the percentage calculation of the upward sliding distance and then changing accordingly.

mScrollParentView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
            @Override
            public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                float v1 = scrollY / topHeight;
                if (0 <= v1 && v1 <= 1.1) {
                    mWaveView.changeWave(v1);
                    mCircleView.changeCircle(v1);
                }
            }
        });

It is a change made in the scroll monitor of the parent view, topHeight is the distance the drawer needs to scroll.

Conclusion

The animations I’ve touched before are all separate modules, which start and end directly. Like this time, they need to be dynamically changed and multiple combinations are encountered for the first time (the scum is right), so I’m also learning by While writing, there may be many places that are not very appropriate, and I hope that the big guys can point out that they can learn and make progress together.

In fact, the current effect has been greatly changed. At first, the height of the Bezier curve was taken as the entire height, and then the middle point was changed to concave downward, but the outer circle had to be exactly half above it and half below it, in this position In fact, it is not easy to adapt, so I changed it to the current one.

Through the realization of this animation, I have made progress not only in customizing views, animations, but also in some ways of thinking, which is quite important. There is another animation in the project, let's talk about it in the next part~

Guess you like

Origin blog.csdn.net/ajsliu1233/article/details/108467755