Skip to content

Instantly share code, notes, and snippets.

@paulproteus
Last active August 3, 2020 04:47
Show Gist options
  • Save paulproteus/5ab6da0ae71b2614221e949b79a30c25 to your computer and use it in GitHub Desktop.
Save paulproteus/5ab6da0ae71b2614221e949b79a30c25 to your computer and use it in GitHub Desktop.
Swipe refresh app
<!--
Copyright 2013 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:weightSum="20"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical">
<ScrollView
android:gravity="top"
android:layout_gravity="top"
android:id="@+id/dismissable_scroll_view"
android:layout_width="match_parent"
android:layout_weight="19"
android:layout_height="match_parent">
<LinearLayout android:id="@+id/dismissable_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</ScrollView>
</LinearLayout>
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.swipedismiss;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.text.SpannableString;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
public class MainActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set up ListView example
String[] items = new String[20];
for (int i = 0; i < items.length; i++) {
items[i] = "Item " + (i + 1);
}
// Set up normal ViewGroup example
final ViewGroup dismissableScrollView = (ViewGroup) findViewById(R.id.dismissable_scroll_view);
final ViewGroup oldParent = (ViewGroup) dismissableScrollView.getParent();
oldParent.removeView(dismissableScrollView);
final SwipeRefreshLayout swipeRefreshWrapper = new SwipeRefreshLayout(this);
LinearLayout.LayoutParams scrollViewParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
scrollViewParams.gravity = Gravity.TOP;
scrollViewParams.weight = 19;
swipeRefreshWrapper.addView(dismissableScrollView);
final TextView youClickedOn = new TextView(this);
youClickedOn.setText("Waiting for click");
swipeRefreshWrapper.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
youClickedOn.setText("You refreshed");
swipeRefreshWrapper.setRefreshing(false);
}
});
oldParent.addView(swipeRefreshWrapper, scrollViewParams);
LinearLayout.LayoutParams youClickedOnParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
youClickedOn.setTextSize(16);
youClickedOn.setGravity(Gravity.CENTER_HORIZONTAL);
//youClickedOnParams.gravity = Gravity.BOTTOM | Gravity.CENTER_VERTICAL;
//youClickedOnParams.height = 16;
youClickedOnParams.weight = 1;
oldParent.addView(youClickedOn, youClickedOnParams);
final ViewGroup dismissableContainer = (ViewGroup) findViewById(R.id.dismissable_container);
for (int i = 0; i < items.length; i++) {
makeRow(dismissableContainer, i, youClickedOn);
}
}
private void setListeners(View v, SwipeDismissTouchListener onTouch, View.OnClickListener onClick) {
v.setOnTouchListener(onTouch);
v.setOnClickListener(onClick);
}
private void makeRow(final ViewGroup dismissableContainer, int i, final TextView youClickedOn) {
final String buttonAlertText = "Clicked on row " + (i + 1);
// Use a FrameLayout for the row. Add two Views to it: rowBackground and rowForeground.
final FrameLayout entireRow = new FrameLayout(this);
dismissableContainer.addView(entireRow);
// Create the background: for now, just some centered text.
final LinearLayout rowBackground = new LinearLayout(this);
entireRow.addView(rowBackground);
rowBackground.setBackgroundColor(0xFFFF2222);
final TextView backgroundText = new TextView(this);
backgroundText.setText("Delete");
backgroundText.setTextSize(20);
LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
);
textParams.setMarginStart(15);
rowBackground.addView(backgroundText, textParams);
backgroundText.setGravity(Gravity.CENTER_VERTICAL);
// Create the foreground.
final RelativeLayout rowForeground = new RelativeLayout(this);
TypedValue typedValue = new TypedValue();
int colorWindowBackground = getResources().getColor(android.R.color.background_light);
if (getTheme().resolveAttribute(android.R.attr.windowBackground, typedValue, true))
{
// how to get color?
colorWindowBackground = typedValue.data;// **just add this line to your code!!**
}
rowForeground.setBackgroundColor(colorWindowBackground);
entireRow.addView(rowForeground, new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
// Create a swipeDismissTouchListener which all the foreground elements will use -- this ensures a
// swipe on any foreground element swipes the entire foreground.
SwipeDismissTouchListener onTouch = new SwipeDismissTouchListener(
rowForeground,
null,
new SwipeDismissTouchListener.DismissCallbacks() {
@Override
public boolean canDismiss(Object token) {
return true;
}
@Override
public void onDismiss(View view, Object token) {
dismissableContainer.removeView(entireRow);
}
});
View.OnClickListener onClick = new View.OnClickListener() {
@Override
public void onClick(View v) {
youClickedOn.setText(buttonAlertText);
}
};
ImageView brutus = new ImageView(this);
setListeners(brutus, onTouch, onClick);
brutus.setOnTouchListener(onTouch);
brutus.setImageResource(R.drawable.brutus);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
params.width = 150;
params.setMargins(25, 0, 25, 0);
params.height = 250;
brutus.setScaleType(ImageView.ScaleType.FIT_CENTER);
rowForeground.addView(brutus, params);
setListeners(rowForeground, onTouch, onClick);
// Create vertically split text layout
final LinearLayout textContainer = new LinearLayout(this);
RelativeLayout.LayoutParams textContainerParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
textContainerParams.height = 250;
textContainerParams.setMargins(25 + 25 + 150, 0, 0, 0);
rowForeground.addView(textContainer, textContainerParams);
textContainer.setOrientation(LinearLayout.VERTICAL);
textContainer.setWeightSum(2);
// Create top & bottom text; add them to layout.
final TextView topText = new TextView(this);
topText.setText("Some top text " + (i + 1));
topText.setTextSize(20);
topText.setTextColor(getResources().getColor(android.R.color.black));
final TextView bottomText = new TextView(this);
bottomText.setTextColor(getResources().getColor(android.R.color.black));
bottomText.setText("A second row");
bottomText.setTextSize(16);
LinearLayout.LayoutParams topTextParams = new LinearLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
topTextParams.weight = 1;
topText.setGravity(Gravity.BOTTOM);
textContainer.addView(topText, topTextParams);
LinearLayout.LayoutParams bottomTextParams = new LinearLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
bottomTextParams.weight = 1;
bottomText.setGravity(Gravity.TOP);
bottomTextParams.gravity = Gravity.TOP;
textContainer.addView(bottomText, bottomTextParams);
setListeners(topText, onTouch, onClick);
setListeners(bottomText, onTouch, onClick);
}
}
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.swipedismiss;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ListActivity;
import android.app.ListFragment;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.RelativeLayout;
/**
* A {@link View.OnTouchListener} that makes any {@link View} dismissable when the
* user swipes (drags her finger) horizontally across the view.
*
* <p><em>For {@link ListView} list items that don't manage their own touch events.</em></p>
*
* <p>Example usage:</p>
*
* <pre>
* view.setOnTouchListener(new SwipeDismissTouchListener(
* view,
* null, // Optional token/cookie object
* new SwipeDismissTouchListener.OnDismissCallback() {
* public void onDismiss(View view, Object token) {
* parent.removeView(view);
* }
* }));
* </pre>
*
* <p>This class Requires API level 12 or later due to use of {@link
* android.view.ViewPropertyAnimator}.</p>
*/
public class SwipeDismissTouchListener implements View.OnTouchListener {
// Cached ViewConfiguration and system-wide constant values
private int mSlop;
private int mMinFlingVelocity;
private int mMaxFlingVelocity;
private long mAnimationTime;
// Fixed properties
private View mView;
private DismissCallbacks mCallbacks;
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
// Transient properties
private float mDownX;
private float mDownY;
private boolean mSwiping;
private int mSwipingSlop;
private Object mToken;
private VelocityTracker mVelocityTracker;
private float mTranslationX;
/**
* The callback interface used by {@link SwipeDismissTouchListener} to inform its client
* about a successful dismissal of the view for which it was created.
*/
public interface DismissCallbacks {
/**
* Called to determine whether the view can be dismissed.
*/
boolean canDismiss(Object token);
/**
* Called when the user has indicated they she would like to dismiss the view.
*
* @param view The originating {@link View} to be dismissed.
* @param token The optional token passed to this object's constructor.
*/
void onDismiss(View view, Object token);
}
/**
* Constructs a new swipe-to-dismiss touch listener for the given view.
*
* @param view The view to make dismissable.
* @param token An optional token/cookie object to be passed through to the callback.
* @param callbacks The callback to trigger when the user has indicated that she would like to
* dismiss this view.
*/
public SwipeDismissTouchListener(View view, Object token, DismissCallbacks callbacks) {
ViewConfiguration vc = ViewConfiguration.get(view.getContext());
mSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
mAnimationTime = view.getContext().getResources().getInteger(
android.R.integer.config_shortAnimTime);
mView = view;
mToken = token;
mCallbacks = callbacks;
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
// offset because the view is translated during swipe
motionEvent.offsetLocation(mTranslationX, 0);
if (mViewWidth < 2) {
mViewWidth = mView.getWidth();
}
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
// TODO: ensure this is a finger, and set a flag
mDownX = motionEvent.getRawX();
mDownY = motionEvent.getRawY();
if (mCallbacks.canDismiss(mToken)) {
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);
}
return false;
}
case MotionEvent.ACTION_UP: {
if (mVelocityTracker == null) {
break;
}
float deltaX = motionEvent.getRawX() - mDownX;
mVelocityTracker.addMovement(motionEvent);
mVelocityTracker.computeCurrentVelocity(1000);
float velocityX = mVelocityTracker.getXVelocity();
float absVelocityX = Math.abs(velocityX);
float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
boolean dismiss = false;
boolean dismissRight = false;
if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) {
dismiss = true;
dismissRight = deltaX > 0;
} else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
&& absVelocityY < absVelocityX
&& absVelocityY < absVelocityX && mSwiping) {
// dismiss only if flinging in the same direction as dragging
dismiss = (velocityX < 0) == (deltaX < 0);
dismissRight = mVelocityTracker.getXVelocity() > 0;
}
if (dismiss) {
// dismiss
mView.animate()
.translationX(dismissRight ? mViewWidth : -mViewWidth)
.alpha(0)
.setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
performDismiss();
}
});
} else if (mSwiping) {
mView.animate()
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mTranslationX = 0;
mDownX = 0;
mDownY = 0;
mSwiping = false;
break;
}
case MotionEvent.ACTION_CANCEL: {
if (mVelocityTracker == null) {
break;
}
mView.animate()
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
mVelocityTracker.recycle();
mVelocityTracker = null;
mTranslationX = 0;
mDownX = 0;
mDownY = 0;
mSwiping = false;
break;
}
case MotionEvent.ACTION_MOVE: {
if (mVelocityTracker == null) {
break;
}
mVelocityTracker.addMovement(motionEvent);
float deltaX = motionEvent.getRawX() - mDownX;
float deltaY = motionEvent.getRawY() - mDownY;
if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) {
mSwiping = true;
mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
mView.getParent().requestDisallowInterceptTouchEvent(true);
// Cancel listview's touch // XXX still required?
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
(motionEvent.getActionIndex() <<
MotionEvent.ACTION_POINTER_INDEX_SHIFT));
mView.onTouchEvent(cancelEvent);
cancelEvent.recycle();
}
if (mSwiping) {
mTranslationX = deltaX;
mView.setTranslationX(deltaX - mSwipingSlop);
// TODO: use an ease-out interpolator or such
//mView.setAlpha(Math.max(0f, Math.min(1f,
// 1f - 2f * Math.abs(deltaX) / mViewWidth)));
return false; // was true
}
break;
}
}
return false;
}
private void performDismiss() {
// Animate the dismissed view to zero-height and then fire the dismiss callback.
// This triggers layout on each animation frame; in the future we may want to do something
// smarter and more performant.
final ViewGroup.LayoutParams lp = mView.getLayoutParams();
final int originalHeight = mView.getHeight();
ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCallbacks.onDismiss(mView, mToken);
// Reset view presentation
mView.setAlpha(1f);
mView.setTranslationX(0);
lp.height = originalHeight;
mView.setLayoutParams(lp);
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
mView.setLayoutParams(lp);
}
});
animator.start();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment