above
I saw the side slider bar of Cooldog app is interesting, with visual scrolling difference and zoom effect, so I tried to achieve one myself. This component could be implemented using a HorScrollView, but using a HorScrollView would still overwrite the touch event, and the HorScrollView doesn’t help the control at all, so use a lighter ViewGroup instead.
Let’s look at the effect first
How to achieve visual scrolling difference effect
My implementation method is rather stupid, in layout according to a sliding parameter offset to carry out layout dislocation increment. The layout code is as follows
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// to layout menu view and content view
if(contentView ! =null) {
int contentHeight = contentView.getMeasuredHeight();
final int contentLeft = (int) (l + slideOffset * MAX_DRAG_FACTOR * (r - l));
final int contentRight = contentLeft + contentView.getMeasuredWidth();
final int contentTop = t + (b - t - contentHeight) / 2;
final int contentBottom = contentTop + contentHeight;
contentView.layout(contentLeft, contentTop, contentRight, contentBottom);
}
if(slideMenuView ! =null) {
final int slideMenuWidth = slideMenuView.getMeasuredWidth();
final int slideMenuHeight = slideMenuView.getMeasuredHeight();
// Poor visual scrolling
final int menuLeft = (int) (l - (1 - slideOffset) * MAX_DRAG_FACTOR * (r - l) * slideMenuParallaxOffset);
final intmenuRight = menuLeft + slideMenuWidth; slideMenuView.layout(menuLeft, t, menuRight, t + slideMenuHeight); }}Copy the code
A contentView and a menuView are used for layout, but where are they retrieved by the control? Or, how does the control know which is contentView and which is menuVIew? Here, I use the method of getting the child control ID based on attR. As shown in the figure below
<com.kongdy.slidemenulib.SlideMenuLayout
android:id="@+id/sml_menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"
app:sml_content_id="@+id/cl_content"
app:sml_menu_id="@+id/cl_slide_menu"
app:sml_scale_mode="true">
<android.support.constraint.ConstraintLayout
android:id="@id/cl_content">.</android.support.constraint.ConstraintLayout>
<android.support.constraint.ConstraintLayout
android:id="@id/cl_slide_menu">.</android.support.constraint.ConstraintLayout>
</com.kongdy.slidemenulib.SlideMenuLayout>
Copy the code
Assign the control ID of the menuView and contentView to the property. This is not the end, however, because we get two ids in the constructor, but we can’t get either control because the layout is not inflate completed. But, thankfully, Android provides us with this method. As follows:
@Override
protected void onFinishInflate(a) {
super.onFinishInflate();
slideMenuView = findViewById(slideMenuId);
contentView = findViewById(contentViewId);
if (null! = contentView) bringChildToFront(contentView); }Copy the code
We also use the bringChildToFront method, which is provided by the viewGroup. Let’s look at this method:
@Override
public void bringChildToFront(View child) {
final int index = indexOfChild(child);
if (index >= 0) {
removeFromArray(index);
addInArray(child, mChildrenCount);
child.mParent = this; requestLayout(); invalidate(); }}Copy the code
This method takes the target child view out of the childiList and puts it back at the end of the childList, so when the viewGroup is rendering it, it will put it in the last rendering, and it will be displayed at the top. This ensures that our contentView is always displayed at the top of our current viewGroup.
Handling touch events
Before the Android picture clipping Mosaic implementation (two) : touch implementation explained the process of touch. In this control, the distribution mechanism of viewGroup has been very perfect, we do not need to rewrite dispatchTouchEvent, only need to write onInterceptTouchEvent to determine whether to intercept. The code for onInterceptTouchEvent is as follows:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// handle weather intercept touch event
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
Rect rect = new Rect();
contentView.getDrawingRect(rect);
final int touchDownX = (int) ev.getX();
final int touchDownY = (int) ev.getY();
if (rect.contains(touchDownX, touchDownY)) {
viewMode = VIEW_MODE_TOUCH;
return true; }}break;
case MotionEvent.ACTION_MOVE: {
if (viewMode == VIEW_MODE_DRAG)
return true;
if (viewMode == VIEW_MODE_TOUCH) {
Rect rect = new Rect();
contentView.getDrawingRect(rect);
final int touchDownX = (int) ev.getX();
final int touchDownY = (int) ev.getY();
if (rect.contains(touchDownX, touchDownY)) {
viewMode = VIEW_MODE_DRAG;
final ViewParent viewParent = getParent();
if(viewParent ! =null)
viewParent.requestDisallowInterceptTouchEvent(false);
return true;
} else{ resetTouchMode(); }}}break;
}
return super.onInterceptTouchEvent(ev);
}
Copy the code
So we’re checking whether the touch point that we dropped is inside the contentView, and then we’re checking whether the touch point that we first slid is still inside the contentView, and if both are true, Call requestDisallowInterceptTouchEvent method request parent don’t intercept the next touch events themselves, and returns true, the touch events to the touchEvent viewGroup to deal with. Here’s the code inside the touchEvent:
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
preTouchX = event.getX();
preTouchY = event.getY();
isClickEvent = true;
break;
case MotionEvent.ACTION_MOVE: {
final float currentTouchX = event.getX();
final float offsetX = currentTouchX - preTouchX;
if(Math.abs(offsetX) > touchSlop || ! isClickEvent) { isClickEvent =false;
int contentLeft = contentView.getLeft();
int preCalcLeft = (int) (contentLeft + offsetX);
if (preCalcLeft >= 0 && preCalcLeft <= getWidth() * MAX_DRAG_FACTOR) {
slideOffset = preCalcLeft / (getWidth() * MAX_DRAG_FACTOR);
reDraw();
}
preTouchX = currentTouchX;
} else {
isClickEvent = true; }}break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
Rect contentViewRect = new Rect();
contentView.getDrawingRect(contentViewRect);
if(isClickEvent && isOpen() && contentViewRect.contains((int)event.getX(),(int)event.getY())) {
animToClose();
} else {
int contentLeft = contentView.getLeft();
final int currentWidth = getWidth();
final int halfWidth = currentWidth / 2;
int animFactor = (contentLeft + halfWidth) / currentWidth;
if (animFactor > 0) {
animToOpen();
} else{ animToClose(); } resetTouchMode(); }}break;
}
return true;
}
Copy the code
Here, we first calculate offsetX, the moving distance between the current touch point and the last touch point, and then judge whether this offsetX is greater than touchSlop, which is the minimum sliding value obtained from the system in the constructor. When this value is exceeded, we judge it to slide and set isClickEvent to false, otherwise isClickEvent is set to true, which corresponds to the click event. After the drag state is entered, we anticipate the left value of the contentView. If this value is less than the left edge, or greater than the maximum slide distance to the right, it is not allowed, although the preCalcLeft left is calculated as the current slide displacement rate for global use.
animation
Finally, when we touch lift or cancel, we will do a sliding animation. The animation is very simple. I will post the code here:
public void animToClose(a) {
if (viewMode == VIEW_MODE_ANIM)
return;
viewMode = VIEW_MODE_ANIM;
Animator valueAnimator = createValueAnim(slideOffset, 0f, SLIDE_MODE_CLOSE);
valueAnimator.start();
}
public void animToOpen(a) {
if (viewMode == VIEW_MODE_ANIM)
return;
viewMode = VIEW_MODE_ANIM;
Animator valueAnimator = createValueAnim(slideOffset, 1f, SLIDE_MODE_OPEN);
valueAnimator.start();
}
private Animator createValueAnim(float startValue, float endValue, final int result_mode) {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(startValue, endValue);
valueAnimator.setDuration(DEFAULT_ANIMATION_TIME);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
slideOffset = (float) animation.getAnimatedValue(); reDraw(); }}); valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
viewMode = VIEW_MODE_IDLE;
slideMode = result_mode;
}
@Override
public void onAnimationCancel(Animator animation) { viewMode = VIEW_MODE_IDLE; slideMode = result_mode; }});return valueAnimator;
}
Copy the code
How to use
Start by adding your project’s root directory gradle:
allprojects {
repositories {
...
maven { url 'https://jitpack.io'}}}Copy the code
Then add the dependency:
dependencies {
compile 'com. Making. Kongdy: SlideMenuLayout: v1.0.2'
}
Copy the code
In the project, the XML tag is declared as follows:
<com.kongdy.slidemenulib.SlideMenuLayout
android:id="@+id/sml_menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"
app:sml_content_id="@+id/cl_content"
app:sml_menu_id="@+id/cl_slide_menu"
app:sml_scale_mode="true">
<android.support.constraint.ConstraintLayout
android:id="@id/cl_content">.</android.support.constraint.ConstraintLayout>
<android.support.constraint.ConstraintLayout
android:id="@id/cl_slide_menu">.</android.support.constraint.ConstraintLayout>
</com.kongdy.slidemenulib.SlideMenuLayout>
Copy the code
- App :sml_content_id Content control ID
- App :sml_menu_id ID of a menu control
- App :sml_scale_mode Specifies whether to enable content control scaling
Commonly used method
- AnimToOpen () performs the open menu animation
- AnimToClose () performs the close menu animation
- IsOpen () Indicates whether the menu is currently open
This article code :github.com/Kongdy/Slid… Personal Github address :github.com/Kongdy Personal Gold Digger homepage :juejin.cn/user/289357… CSDN homepage: blog.csdn.net/u014303003