为cardslib添加长按滑动删除(Android)
2020-07-13 04:16
344 查看
如果你够酷的话你肯定知道cardslib,这是一个封装了各种CardView的和作为容器的CardListView,CardGridView的一个android控件库
CardListView中还提供了SwipToDismiss(滑动删除)的功能,十分炫酷,但是某些情况下很容易触发错误操作,而且在使用了viewpager的情况下更是噩梦,为此我们可以为它添加选项,让CardListView支持长按滑动删除。
首先,在cardslib目前版本中有个bug,就是CardListView在滑动删除过程中没有屏蔽掉多点操控,导致在滑动过程中可以通过另外一点上下滑动来强行终止swipe的过程,我们可以通过设置CardListView的MotionEventSplittingEnabled属性来修复这个bug(已经在github中提交了Pull Request ^_^ )
//CardListView.java
protected void init(AttributeSet attrs, int defStyle){
//Init attrs
initAttrs(attrs,defStyle);
//Set divider to 0dp
setDividerHeight(0);
this.setMotionEventSplittingEnabled(false);
}
[/code]
为了将长按删除添加到CardListView中,我们需要捕获CardView的OnLongClick事件,并使用变量mLongClicked来标记使用已经长按,如果mLongClicked为true则开始swipe操作。但是这里需要注意的是我们需要用OnLongClick事件来处理swipetodismiss事件,所以我们不能为item再设置OnLongClickListener了。还有就是我们需要修改CardView的refreshCard方法,因为每次refreshCard之后会覆盖我们用来处理swipetodismiss的OnLongClickListener,我们需要修改下它,让它在refrehCard的时候不覆盖我们的OnLongClickListener;
package it.gmariotti.cardslib.library.view.listener;
/* Copyright 2013 Roman Nurik, Gabriele Mariotti
*
* 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.
*/
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.widget.AbsListView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import it.gmariotti.cardslib.library.internal.Card;
/**
* It is based on Roman Nurik code.
* See this link for original code https://github.com/romannurik/Android-SwipeToDismiss
* </p>
* It provides a SwipeDismissViewTouchListener for a CardList.
* </p>
*
* A {@link View.OnTouchListener} that makes the list items in a {@link ListView}
* dismissable. {@link ListView} is given special treatment because by default it handles touches
* for its list items... i.e. it's in charge of drawing the pressed state (the list selector),
* handling list item clicks, etc.
*
* <p>After creating the listener, the caller should also call
* {@link ListView#setOnScrollListener(AbsListView.OnScrollListener)}, passing
* in the scroll listener returned by {@link #makeScrollListener()}. If a scroll listener is
* already assigned, the caller should still pass scroll changes through to this listener. This will
* ensure that this {@link SwipeDismissListViewTouchListener} is paused during list view
* scrolling.</p>
*
* <p>Example usage:</p>
*
* <pre>
* SwipeDismissListViewTouchListener touchListener =
* new SwipeDismissListViewTouchListener(
* listView,
* new SwipeDismissListViewTouchListener.OnDismissCallback() {
* public void onDismiss(ListView listView, int[] reverseSortedPositions) {
* for (int position : reverseSortedPositions) {
* adapter.remove(adapter.getItem(position));
* }
* adapter.notifyDataSetChanged();
* }
* });
* listView.setOnTouchListener(touchListener);
* listView.setOnScrollListener(touchListener.makeScrollListener());
* </pre>
*
* <p>This class Requires API level 12 or later due to use of {@link
* ViewPropertyAnimator}.</p>
*
*/
public class SwipeDismissListViewTouchListener implements View.OnTouchListener, View.OnLongClickListener {
// Cached ViewConfiguration and system-wide constant values
private int mSlop;
private int mMinFlingVelocity;
private int mMaxFlingVelocity;
private long mAnimationTime;
// Fixed properties
private ListView mListView;
private DismissCallbacks mCallbacks;
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
// Transient properties
private List<PendingDismissData> mPendingDismisses = new ArrayList<PendingDismissData>();
private int mDismissAnimationRefCount = 0;
private float mDownX;
private boolean mSwiping;
private VelocityTracker mVelocityTracker;
private int mDownPosition;
private View mDownView;
private boolean mPaused;
private boolean mLongClicked = false;
private boolean mUseLongClickSwipe = false;
/**
* The callback interface used by {@link SwipeDismissListViewTouchListener} to inform its client
* about a successful dismissal of one or more list item positions.
*/
public interface DismissCallbacks {
/**
* Called to determine whether the given position can be dismissed.
*/
boolean canDismiss(int position,Card card);
/**
* Called when the user has indicated they she would like to dismiss one or more list item
* positions.
*
* @param listView The originating {@link ListView}.
* @param reverseSortedPositions An array of positions to dismiss, sorted in descending
* order for convenience.
*/
void onDismiss(ListView listView, int[] reverseSortedPositions);
}
/**
* Constructs a new swipe-to-dismiss touch listener for the given list view.
*
* @param listView The list view whose items should be dismissable.
* @param callbacks The callback to trigger when the user has indicated that she would like to
* dismiss one or more list items.
*/
public SwipeDismissListViewTouchListener(ListView listView, DismissCallbacks callbacks) {
ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
mSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
mAnimationTime = listView.getContext().getResources().getInteger(
android.R.integer.config_shortAnimTime);
mListView = listView;
mCallbacks = callbacks;
}
/**
* Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
*
* @param enabled Whether or not to watch for gestures.
*/
public void setEnabled(boolean enabled) {
mPaused = !enabled;
}
public void setUseLongClickSwipe(boolean useLongClickSwipe) {
mUseLongClickSwipe = useLongClickSwipe;
}
/**
* Returns an {@link AbsListView.OnScrollListener} to be added to the {@link
* ListView} using {@link ListView#setOnScrollListener(AbsListView.OnScrollListener)}.
* If a scroll listener is already assigned, the caller should still pass scroll changes through
* to this listener. This will ensure that this {@link SwipeDismissListViewTouchListener} is
* paused during list view scrolling.</p>
*
* @see SwipeDismissListViewTouchListener
*/
public AbsListView.OnScrollListener makeScrollListener() {
return new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
if(!mUseLongClickSwipe)
setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
}
@Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
}
};
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (mViewWidth < 2) {
mViewWidth = mListView.getWidth();
}
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mLongClicked = false;
if (mPaused) {
return false;
}
// TODO: ensure this is a finger, and set a flag
// Find the child view that was touched (perform a hit test)
Rect rect = new Rect();
int childCount = mListView.getChildCount();
int[] listViewCoords = new int[2];
mListView.getLocationOnScreen(listViewCoords);
int x = (int) motionEvent.getRawX() - listViewCoords[0];
int y = (int) motionEvent.getRawY() - listViewCoords[1];
View child=null;
for (int i = 0; i < childCount; i++) {
child = mListView.getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
mDownView = child;
break;
}
}
if (mDownView != null) {
mDownX = motionEvent.getRawX();
mDownPosition = mListView.getPositionForView(mDownView);
if (mCallbacks.canDismiss(mDownPosition,(Card) mListView.getAdapter().getItem(mDownPosition))) {
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);
} else {
mDownView = null;
}
}
view.onTouchEvent(motionEvent);
return true;
}
case MotionEvent.ACTION_UP: {
if (mVelocityTracker == null) {
break;
}
mLongClicked = false;
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 * 1/3) {
dismiss = true;
dismissRight = deltaX > 0;
} else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
&& absVelocityY < absVelocityX) {
// dismiss only if flinging in the same direction as dragging
dismiss = (velocityX < 0) == (deltaX < 0);
dismissRight = mVelocityTracker.getXVelocity() > 0;
}
if (dismiss) {
// dismiss
final View downView = mDownView; // mDownView gets null'd before animation ends
final int downPosition = mDownPosition;
++mDismissAnimationRefCount;
mDownView.animate()
.translationX(dismissRight ? mViewWidth : -mViewWidth)
.alpha(0)
.setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
performDismiss(downView, downPosition);
}
});
} else {
// cancel
mDownView.animate()
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}
case MotionEvent.ACTION_MOVE: {
if (mVelocityTracker == null || mPaused || (mUseLongClickSwipe && !mLongClicked)) {
break;
}
mVelocityTracker.addMovement(motionEvent);
float deltaX = motionEvent.getRawX() - mDownX;
if (Math.abs(deltaX) > mSlop) {
mSwiping = true;
mListView.requestDisallowInterceptTouchEvent(true);
// Cancel ListView's touch (un-highlighting the item)
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
(motionEvent.getActionIndex()
<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));
mListView.onTouchEvent(cancelEvent);
cancelEvent.recycle();
}
if (mSwiping) {
mDownView.setTranslationX(deltaX);
mDownView.setAlpha(Math.max(0f, Math.min(1f,
1f - 2f * Math.abs(deltaX) / mViewWidth)));
return true;
}
break;
}
}
return false;
}
class PendingDismissData implements Comparable<PendingDismissData> {
public int position;
public View view;
public PendingDismissData(int position, View view) {
this.position = position;
this.view = view;
}
@Override
public int compareTo(PendingDismissData other) {
// Sort by descending position
return other.position - position;
}
}
private void performDismiss(final View dismissView, final int dismissPosition) {
// Animate the dismissed list item to zero-height and fire the dismiss callback when
// all dismissed list item animations have completed. This triggers layout on each animation
// frame; in the future we may want to do something smarter and more performant.
final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
final int originalHeight = dismissView.getHeight();
ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
--mDismissAnimationRefCount;
if (mDismissAnimationRefCount == 0) {
// No active animations, process all pending dismisses.
// Sort by descending position
Collections.sort(mPendingDismisses);
int[] dismissPositions = new int[mPendingDismisses.size()];
for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
dismissPositions[i] = mPendingDismisses.get(i).position;
}
mCallbacks.onDismiss(mListView, dismissPositions);
ViewGroup.LayoutParams lp;
for (PendingDismissData pendingDismiss : mPendingDismisses) {
// Reset view presentation
pendingDismiss.view.setAlpha(1f);
pendingDismiss.view.setTranslationX(0);
lp = pendingDismiss.view.getLayoutParams();
lp.height = originalHeight;
pendingDismiss.view.setLayoutParams(lp);
}
mPendingDismisses.clear();
}
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
dismissView.setLayoutParams(lp);
}
});
mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView));
animator.start();
}
@Override
public boolean onLongClick(View v) {
mLongClicked = true;
mDownView.setTranslationX(20);
mListView.requestDisallowInterceptTouchEvent(true);
return true;
}
}
[/code]
/*
* ******************************************************************************
* Copyright (c) 2013 Gabriele Mariotti.
*
* 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 it.gmariotti.cardslib.library.internal;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Parcelable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ListView;
import java.util.HashMap;
import java.util.List;
import it.gmariotti.cardslib.library.R;
import it.gmariotti.cardslib.library.internal.base.BaseCardArrayAdapter;
import it.gmariotti.cardslib.library.view.CardListView;
import it.gmariotti.cardslib.library.view.CardView;
import it.gmariotti.cardslib.library.view.listener.SwipeDismissListViewTouchListener;
import it.gmariotti.cardslib.library.view.listener.UndoBarController;
import it.gmariotti.cardslib.library.view.listener.UndoCard;
/**
* Array Adapter for {@link Card} model
* <p/>
* Usage:
* <pre><code>
* ArrayList<Card> cards = new ArrayList<Card>();
* for (int i=0;i<1000;i++){
* CardExample card = new CardExample(getActivity(),"My title "+i,"Inner text "+i);
* cards.add(card);
* }
*
* CardArrayAdapter mCardArrayAdapter = new CardArrayAdapter(getActivity(),cards);
*
* CardListView listView = (CardListView) getActivity().findViewById(R.id.listId);
* listView.setAdapter(mCardArrayAdapter); *
* </code></pre>
* It provides a default layout id for each row @layout/list_card_layout
* Use can easily customize it using card:list_card_layout_resourceID attr in your xml layout:
* <pre><code>
* <it.gmariotti.cardslib.library.view.CardListView
* android:layout_width="match_parent"
* android:layout_height="match_parent"
* android:id="@+id/carddemo_list_gplaycard"
* card:list_card_layout_resourceID="@layout/list_card_thumbnail_layout" />
* </code></pre>
* or:
* <pre><code>
* adapter.setRowLayoutId(list_card_layout_resourceID);
* </code></pre>
* </p>
* @author Gabriele Mariotti (gabri.mariotti@gmail.com)
*/
public class CardArrayAdapter extends BaseCardArrayAdapter implements UndoBarController.UndoListener {
protected static String TAG = "CardArrayAdapter";
/**
* {@link CardListView}
*/
protected CardListView mCardListView;
/**
* Listener invoked when a card is swiped
*/
protected SwipeDismissListViewTouchListener mOnTouchListener;
/**
* Used to enable an undo message after a swipe action
*/
protected boolean mEnableUndo=false;
protected boolean mUseLongClickSwipe = false;
/**
* Undo Controller
*/
protected UndoBarController mUndoBarController;
/**
* Internal Map with all Cards.
* It uses the card id value as key.
*/
protected HashMap<String /* id */,Card> mInternalObjects;
// -------------------------------------------------------------
// Constructors
// -------------------------------------------------------------
/**
* Constructor
*
* @param context The current context.
* @param cards The cards to represent in the ListView.
*/
public CardArrayAdapter(Context context, List<Card> cards) {
super(context, cards);
}
// -------------------------------------------------------------
// Views
// -------------------------------------------------------------
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
CardView mCardView;
Card mCard;
LayoutInflater mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//Retrieve card from items
mCard = (Card) getItem(position);
if (mCard != null) {
int layout = mRowLayoutId;
boolean recycle = false;
//Inflate layout
if (view == null) {
recycle = false;
view = mInflater.inflate(layout, parent, false);
} else {
recycle = true;
}
//Setup card
mCardView = (CardView) view.findViewById(R.id.list_cardId);
if (mCardView != null) {
//It is important to set recycle value for inner layout elements
mCardView.setForceReplaceInnerLayout(Card.equalsInnerLayout(mCardView.getCard(),mCard));
//It is important to set recycle value for performance issue
mCardView.setRecycle(recycle);
//Save original swipeable to prevent cardSwipeListener (listView requires another cardSwipeListener)
//boolean origianlSwipeable = mCard.isSwipeable();
//mCard.setSwipeable(false);
mCardView.setIsInCardListView(true);
mCardView.setCard(mCard);
//Set originalValue
//mCard.setSwipeable(origianlSwipeable);
//If card has an expandable button override animation
if (mCard.getCardHeader() != null && mCard.getCardHeader().isButtonExpandVisible()) {
setupExpandCollapseListAnimation(mCardView);
}
//Setup swipeable animation
setupSwipeableAnimation(mCard, mCardView);
//setupMultiChoice
setupMultichoice(view,mCard,mCardView,position);
}
}
return view;
}
/**
* Sets SwipeAnimation on List
*
* @param card {@link Card}
* @param cardView {@link CardView}
*/
protected void setupSwipeableAnimation(final Card card, CardView cardView) {
if (card.isSwipeable()){
if (mOnTouchListener == null){
mOnTouchListener = new SwipeDismissListViewTouchListener(mCardListView, mCallback);
// Setting this scroll listener is required to ensure that during
// ListView scrolling, we don't look for swipes.
mCardListView.setOnScrollListener(mOnTouchListener.makeScrollListener());
}
mOnTouchListener.setUseLongClickSwipe(mUseLongClickSwipe);
cardView.setOnTouchListener(mOnTouchListener);
if(mUseLongClickSwipe) {
cardView.setOnLongClickListener(mOnTouchListener);
}
}else{
//prevent issue with recycle view
cardView.setOnTouchListener(null);
}
}
public void setUseLongClickSwipe(boolean useLongClickSwipe) {
mUseLongClickSwipe = useLongClickSwipe;
}
/**
* Overrides the default collapse/expand animation in a List
*
* @param cardView {@link CardView}
*/
protected void setupExpandCollapseListAnimation(CardView cardView) {
if (cardView == null) return;
cardView.setOnExpandListAnimatorListener(mCardListView);
}
// -------------------------------------------------------------
// SwipeListener and undo action
// -------------------------------------------------------------
/**
* Listener invoked when a card is swiped
*/
SwipeDismissListViewTouchListener.DismissCallbacks mCallback = new SwipeDismissListViewTouchListener.DismissCallbacks() {
@Override
public boolean canDismiss(int position, Card card) {
return card.isSwipeable();
}
@Override
public void onDismiss(ListView listView, int[] reverseSortedPositions) {
int[] itemPositions=new int[reverseSortedPositions.length];
String[] itemIds=new String[reverseSortedPositions.length];
int i=0;
//Remove cards and notifyDataSetChanged
for (int position : reverseSortedPositions) {
Card card = getItem(position);
itemPositions[i]=position;
itemIds[i]=card.getId();
i++;
remove(card);
if (card.getOnSwipeListener() != null){
card.getOnSwipeListener().onSwipe(card);
}
}
notifyDataSetChanged();
//Check for a undo message to confirm
if (isEnableUndo() && mUndoBarController!=null){
//Show UndoBar
UndoCard itemUndo=new UndoCard(itemPositions,itemIds);
if (getContext()!=null){
Resources res = getContext().getResources();
if (res!=null){
String messageUndoBar = res.getQuantityString(R.plurals.list_card_undo_items, reverseSortedPositions.length, reverseSortedPositions.length);
mUndoBarController.showUndoBar(
false,
messageUndoBar,
itemUndo);
}
}
}
}
};
// -------------------------------------------------------------
// Undo Default Listener
// -------------------------------------------------------------
@Override
public void onUndo(Parcelable token) {
//Restore items in lists (use reverseSortedOrder)
if (token != null) {
UndoCard item = (UndoCard) token;
int[] itemPositions = item.itemPosition;
String[] itemIds = item.itemId;
if (itemPositions != null) {
int end = itemPositions.length;
for (int i = end - 1; i >= 0; i--) {
int itemPosition = itemPositions[i];
String id= itemIds[i];
if (id==null){
Log.w(TAG, "You have to set a id value to use the undo action");
}else{
Card card = mInternalObjects.get(id);
if (card!=null){
insert(card, itemPosition);
notifyDataSetChanged();
if (card.getOnUndoSwipeListListener()!=null)
card.getOnUndoSwipeListListener().onUndoSwipe(card);
}
}
}
}
}
}
// -------------------------------------------------------------
// Getters and Setters
// -------------------------------------------------------------
/**
* @return {@link CardListView}
*/
public CardListView getCardListView() {
return mCardListView;
}
/**
* Sets the {@link CardListView}
*
* @param cardListView cardListView
*/
public void setCardListView(CardListView cardListView) {
this.mCardListView = cardListView;
}
/**
* Indicates if the undo message is enabled after a swipe action
*
* @return <code>true</code> if the undo message is enabled
*/
public boolean isEnableUndo() {
return mEnableUndo;
}
/**
* Enables an undo message after a swipe action
*
* @param enableUndo <code>true</code> to enable an undo message
*/
public void setEnableUndo(boolean enableUndo) {
mEnableUndo = enableUndo;
if (enableUndo) {
mInternalObjects = new HashMap<String, Card>();
for (int i=0;i<getCount();i++) {
Card card = getItem(i);
mInternalObjects.put(card.getId(), card);
}
//Create a UndoController
if (mUndoBarController==null){
View undobar = ((Activity)mContext).findViewById(R.id.list_card_undobar);
if (undobar != null) {
mUndoBarController = new UndoBarController(undobar, this);
}
}
}else{
mUndoBarController=null;
}
}
/**
* Return the UndoBarController for undo action
*
* @return {@link UndoBarController}
*/
public UndoBarController getUndoBarController() {
return mUndoBarController;
}
}
[/code]
/*
* ******************************************************************************
* Copyright (c) 2013 Gabriele Mariotti.
*
* 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 it.gmariotti.cardslib.library.view;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import java.util.HashMap;
import it.gmariotti.cardslib.library.R;
import it.gmariotti.cardslib.library.internal.Card;
import it.gmariotti.cardslib.library.internal.CardExpand;
import it.gmariotti.cardslib.library.internal.CardHeader;
import it.gmariotti.cardslib.library.internal.CardThumbnail;
import it.gmariotti.cardslib.library.view.component.CardHeaderView;
import it.gmariotti.cardslib.library.view.component.CardThumbnailView;
import it.gmariotti.cardslib.library.view.listener.SwipeDismissViewTouchListener;
/**
* Card view
* </p>
* Use an XML layout file to display it.
* </p>
* First, you need an XML layout that will display the Card.
* <pre><code>
* <it.gmariotti.cardslib.library.view.CardView
* android:id="@+id/carddemo_example_card3"
* android:layout_width="match_parent"
* android:layout_height="wrap_content"
* android:layout_marginLeft="12dp"
* android:layout_marginRight="12dp"
* android:layout_marginTop="12dp"/>
* </code></pre>
* Then create a model:
* <pre><code>
*
* //Create a Card
* Card card = new Card(getContext());
*
* //Create a CardHeader
* CardHeader header = new CardHeader(getContext());
*
* //Add Header to card
* card.addCardHeader(header);
*
* </code></pre>
* Last get a reference to the `CardView` from your code, and set your `Card.
* <pre><code>
* //Set card in the cardView
* CardView cardView = (CardView) getActivity().findViewById(R.id.carddemo);
*
* cardView.setCard(card);
* </code></pre>
* You can easily build your layout.
* </p>
* The quickest way to start with this would be to copy one of this files and create your layout.
* Then you can inflate your layout in the `CardView` using the attr: `card:card_layout_resourceID="@layout/my_layout`
* Example:
* <pre><code>
* <it.gmariotti.cardslib.library.view.CardView
* android:id="@+id/carddemo_thumb_url"
* android:layout_width="match_parent"
* android:layout_height="wrap_content"
* android:layout_marginLeft="12dp"
* android:layout_marginRight="12dp"
* card:card_layout_resourceID="@layout/card_thumbnail_layout"
* android:layout_marginTop="12dp"/>
* </code></pre>
* </p>
* @author Gabriele Mariotti (gabri.mariotti@gmail.com)
*/
public class CardView extends BaseCardView {
//--------------------------------------------------------------------------
//
//--------------------------------------------------------------------------
/**
* {@link CardHeader} model
*/
protected CardHeader mCardHeader;
/**
* {@link CardThumbnail} model
*/
protected CardThumbnail mCardThumbnail;
/**
* {@link CardExpand} model
*/
protected CardExpand mCardExpand;
//--------------------------------------------------------------------------
// Layout
//--------------------------------------------------------------------------
/**
* Main Layout
*/
protected View mInternalMainCardLayout;
/**
* Content Layout
*/
protected View mInternalContentLayout;
/**
* Inner View.
*/
protected View mInternalInnerView;
/**
* Hidden layout used by expand/collapse action
*/
protected View mInternalExpandLayout;
/**
* Expand Inner view
*/
protected View mInternalExpandInnerView;
/** Animator to expand/collapse */
protected Animator mExpandAnimator;
/**
* Listener invoked when Expand Animator starts
* It is used internally
*/
protected OnExpandListAnimatorListener mOnExpandListAnimatorListener;
//--------------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------------
public CardView(Context context) {
super(context);
}
public CardView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
//--------------------------------------------------------------------------
// Init
//--------------------------------------------------------------------------
/**
* Init custom attrs.
*
* @param attrs
* @param defStyle
*/
protected void initAttrs(AttributeSet attrs, int defStyle) {
card_layout_resourceID = R.layout.card_layout;
TypedArray a = getContext().getTheme().obtainStyledAttributes(
attrs, R.styleable.card_options, defStyle, defStyle);
try {
card_layout_resourceID = a.getResourceId(R.styleable.card_options_card_layout_resourceID, this.card_layout_resourceID);
} finally {
a.recycle();
}
}
//--------------------------------------------------------------------------
// Card
//--------------------------------------------------------------------------
/**
* Add a {@link Card}.
* It is very important to set all values and all components before launch this method.
*
* @param card {@link Card} model
*/
@Override
public void setCard(Card card){
super.setCard(card);
if (card!=null){
mCardHeader=card.getCardHeader();
mCardThumbnail=card.getCardThumbnail();
mCardExpand=card.getCardExpand();
}
//Retrieve all IDs
if (!isRecycle()){
retrieveLayoutIDs();
}
//Build UI
buildUI();
}
/**
* Refreshes the card content (it doesn't inflate layouts again)
*
* @param card
*/
public void refreshCard(Card card) {
if(mIsInCardListView)
mPassSetupListener = true;
mIsRecycle=true;
setCard(card);
mIsRecycle=false;
mPassSetupListener = false;
}
private boolean mIsInCardListView = false;
private boolean mPassSetupListener = false;
public void setIsInCardListView(boolean isInCardListView) {
mIsInCardListView = isInCardListView;
}
/**
* Refreshes the card content and replaces the inner layout elements (it inflates layouts again!)
*
* @param card
*/
public void replaceCard(Card card) {
mForceReplaceInnerLayout=true;
refreshCard(card);
mForceReplaceInnerLayout=false;
}
//--------------------------------------------------------------------------
// Setup methods
//--------------------------------------------------------------------------
@Override
protected void buildUI() {
super.buildUI();
mCard.setCardView(this);
//Setup Header view
setupHeaderView();
//Setup Main View
setupMainView();
//setup Thumbnail
setupThumbnailView();
//Setup Expand View
setupExpandView();
if(!mPassSetupListener)
//Setup Listeners
setupListeners();
//Setup Drawable Resources
setupDrawableResources();
}
/**
* Retrieve all Layouts IDs
*/
@Override
protected void retrieveLayoutIDs(){
super.retrieveLayoutIDs();
//Main Layout
mInternalMainCardLayout = (View) findViewById(R.id.card_main_layout);
//Get HeaderLayout
mInternalHeaderLayout = (CardHeaderView) findViewById(R.id.card_header_layout);
//Get ExpandHiddenView
mInternalExpandLayout = (View) findViewById(R.id.card_content_expand_layout);
//Get ContentLayout
mInternalContentLayout = (View) findViewById(R.id.card_main_content_layout);
//Get ThumbnailLayout
mInternalThumbnailLayout = (CardThumbnailView) findViewById(R.id.card_thumbnail_layout);
}
/**
* Setup Header View
*/
protected void setupHeaderView(){
if (mCardHeader!=null){
if (mInternalHeaderLayout !=null){
mInternalHeaderLayout.setVisibility(VISIBLE);
//Set recycle value (very important in a ListView)
mInternalHeaderLayout.setRecycle(isRecycle());
mInternalHeaderLayout.setForceReplaceInnerLayout(isForceReplaceInnerLayout());
//Add Header View
mInternalHeaderLayout.addCardHeader(mCardHeader);
//Config ExpandLayout and its animation
if (mInternalExpandLayout !=null && mCardHeader.isButtonExpandVisible() ){
//Create the expand/collapse animator
mInternalExpandLayout.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mInternalExpandLayout.getViewTreeObserver().removeOnPreDrawListener(this);
//mInternalExpandLayout.setVisibility(View.GONE);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mInternalExpandLayout.measure(widthSpec, heightSpec);
final int widthSpecCard = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpecCard = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mCollapsedHeight = getMeasuredHeight();
mExpandAnimator = createSlideAnimator(0, mInternalExpandLayout.getMeasuredHeight());
return true;
}
});
}
//Setup action and callback
setupExpandCollapseAction();
}
}else{
//No header. Hide layouts
if (mInternalHeaderLayout !=null){
mInternalHeaderLayout.setVisibility(GONE);
mInternalExpandLayout.setVisibility(View.GONE);
if (isForceReplaceInnerLayout()){
mInternalHeaderLayout.addCardHeader(null);
//mInternalHeaderLayout.removeAllViews();
}
}
}
}
/**
* Setup the Main View
*/
protected void setupMainView(){
if (mInternalContentLayout !=null){
ViewGroup mParentGroup=null;
try{
mParentGroup = (ViewGroup) mInternalContentLayout;
}catch (Exception e){
setRecycle(false);
}
//Check if view can be recycled
//It can happen in a listView, and improves performances
if (!isRecycle() || isForceReplaceInnerLayout()){
if (isForceReplaceInnerLayout() && mInternalContentLayout!=null && mInternalInnerView!=null)
((ViewGroup)mInternalContentLayout).removeView(mInternalInnerView);
mInternalInnerView=mCard.getInnerView(getContext(), (ViewGroup) mInternalContentLayout);
}else{
//View can be recycled.
//Only setup Inner Elements
if (mCard.getInnerLayout()>-1)
mCard.setupInnerViewElements(mParentGroup,mInternalInnerView);
}
}
}
/**
* Setup the Thumbnail View
*/
protected void setupThumbnailView() {
if (mInternalThumbnailLayout!=null){
if (mCardThumbnail!=null){
mInternalThumbnailLayout.setVisibility(VISIBLE);
mInternalThumbnailLayout.setRecycle(isRecycle());
mInternalThumbnailLayout.setForceReplaceInnerLayout(isForceReplaceInnerLayout());
mInternalThumbnailLayout.addCardThumbnail(mCardThumbnail);
}else{
mInternalThumbnailLayout.setVisibility(GONE);
}
}
}
/**
* Setup Drawable Resources
*/
protected void setupDrawableResources() {
//Card
if (mCard!=null){
if (mCard.getBackgroundResourceId()!=0){
changeBackgroundResourceId(mCard.getBackgroundResourceId());
}else if (mCard.getBackgroundResource()!=null){
changeBackgroundResource(mCard.getBackgroundResource());
}
}
}
//--------------------------------------------------------------------------
// Listeners
//--------------------------------------------------------------------------
/**
* Setup All listeners
*/
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
protected void setupListeners(){
//Swipe listener
if (mCard.isSwipeable() && !mIsInCardListView){
this.setOnTouchListener(new SwipeDismissViewTouchListener(this, mCard,new SwipeDismissViewTouchListener.DismissCallbacks() {
@Override
public boolean canDismiss(Card card) {
return card.isSwipeable();
}
@Override
public void onDismiss(CardView cardView, Card card) {
final ViewGroup vg = (ViewGroup)(cardView.getParent());
if (vg!=null){
vg.removeView(cardView);
card.onSwipeCard();
}
}
}));
}else{
this.setOnTouchListener(null);
}
//OnClick listeners and partial listener
//Reset Partial Listeners
resetPartialListeners();
if (mCard.isClickable()){
//Set the onClickListener
if(!mCard.isMultiChoiceEnabled()){
if (mCard.getOnClickListener() != null) {
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mCard.getOnClickListener()!=null)
mCard.getOnClickListener().onClick(mCard,v);
}
});
//Prevent multiple events
//if (!mCard.isSwipeable() && mCard.getOnSwipeListener() == null) {
// this.setClickable(true);
//}
}else{
HashMap<Integer,Card.OnCardClickListener> mMultipleOnClickListner=mCard.getMultipleOnClickListener();
if (mMultipleOnClickListner!=null && !mMultipleOnClickListner.isEmpty()){
for (int key:mMultipleOnClickListner.keySet()){
View viewClickable= decodeAreaOnClickListener(key);
final Card.OnCardClickListener mListener=mMultipleOnClickListner.get(key);
if (viewClickable!=null){
//Add listener to this view
viewClickable.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//Callback to card listener
if (mListener!=null)
mListener.onClick(mCard,v);
}
});
//Add Selector to this view
if (key > Card.CLICK_LISTENER_ALL_VIEW) {
if (Build.VERSION.SDK_INT >= 16){
viewClickable.setBackground(getResources().getDrawable(R.drawable.card_selector));
} else {
viewClickable.setBackgroundDrawable(getResources().getDrawable(R.drawable.card_selector));
}
}
}
}
}else{
//There aren't listners
this.setClickable(false);
}
}
}
}else{
this.setClickable(false);
}
//LongClick listener
if(mCard.isLongClickable()){
this.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mCard.getOnLongClickListener()!=null)
return mCard.getOnLongClickListener().onLongClick(mCard,v);
return false;
}
});
}else{
this.setLongClickable(false);
}
}
/**
* Reset all partial listeners
*/
protected void resetPartialListeners() {
View viewClickable= decodeAreaOnClickListener(Card.CLICK_LISTENER_HEADER_VIEW);
if (viewClickable!=null)
viewClickable.setClickable(false);
viewClickable= decodeAreaOnClickListener(Card.CLICK_LISTENER_THUMBNAIL_VIEW);
if (viewClickable!=null)
viewClickable.setClickable(false);
viewClickable= decodeAreaOnClickListener(Card.CLICK_LISTENER_CONTENT_VIEW);
if (viewClickable!=null)
viewClickable.setClickable(false);
}
/**
*
* @param area
* @return
*/
protected View decodeAreaOnClickListener(int area){
if (area<Card.CLICK_LISTENER_ALL_VIEW && area>Card.CLICK_LISTENER_CONTENT_VIEW)
return null;
View view = null;
switch (area){
case Card.CLICK_LISTENER_ALL_VIEW :
view=this;
break;
case Card.CLICK_LISTENER_HEADER_VIEW :
view=mInternalHeaderLayout;
break;
case Card.CLICK_LISTENER_THUMBNAIL_VIEW:
view=mInternalThumbnailLayout;
break;
case Card.CLICK_LISTENER_CONTENT_VIEW:
view=mInternalContentLayout;
break;
default:
break;
}
return view;
}
//--------------------------------------------------------------------------
// Expandable Actions and Listeners
//--------------------------------------------------------------------------
protected int mCollapsedHeight;
protected int mExpandedHeight=-1;
/**
* Add ClickListener to expand and collapse hidden view
*/
protected void setupExpandCollapseAction() {
if (mInternalExpandLayout!=null){
mInternalExpandLayout.setVisibility(View.GONE);
if (mCardHeader!=null){
if (mCardHeader.isButtonExpandVisible()){
mInternalHeaderLayout.setOnClickExpandCollapseActionListener(new TitleViewOnClickListener(mInternalExpandLayout,mCard));
if (isExpanded()){
//Make layout visible and button selected
mInternalExpandLayout.setVisibility(View.VISIBLE);
if(mInternalHeaderLayout.getImageButtonExpand()!=null)
mInternalHeaderLayout.getImageButtonExpand().setSelected(true);
}else{
//Make layout hidden and button not selected
mInternalExpandLayout.setVisibility(View.GONE);
if(mInternalHeaderLayout.getImageButtonExpand()!=null)
mInternalHeaderLayout.getImageButtonExpand().setSelected(false);
}
}
}
}
}
/**
* Setup Expand View
*/
protected void setupExpandView(){
if (mInternalExpandLayout!=null && mCardExpand!=null){
//Check if view can be recycled
//It can happen in a listView, and improves performances
if (!isRecycle() || isForceReplaceInnerLayout()){
if (isForceReplaceInnerLayout() && mInternalExpandLayout!=null && mInternalExpandInnerView!=null)
((ViewGroup)mInternalExpandLayout).removeView(mInternalExpandInnerView);
mInternalExpandInnerView=mCardExpand.getInnerView(getContext(),(ViewGroup) mInternalExpandLayout);
}else{
//View can be recycled.
//Only setup Inner Elements
if (mCardExpand.getInnerLayout()>-1)
mCardExpand.setupInnerViewElements((ViewGroup)mInternalExpandLayout,mInternalExpandInnerView);
}
}
}
/**
* Listener to expand/collapse hidden Expand Layout
* It starts animation
*/
protected class TitleViewOnClickListener implements View.OnClickListener {
private View mContentParent;
private Card mCard;
private TitleViewOnClickListener(View contentParent,Card card) {
this.mContentParent = contentParent;
this.mCard=card;
}
@Override
public void onClick(View view) {
boolean isVisible = mContentParent.getVisibility() == View.VISIBLE;
if (isVisible) {
animateCollapsing();
view.setSelected(false);
} else {
animateExpanding();
view.setSelected(true);
}
}
/**
* Expanding animator.
*/
private void animateExpanding() {
if (getOnExpandListAnimatorListener()!=null){
//List Animator
getOnExpandListAnimatorListener().onExpandStart(mCard.getCardView(), mContentParent);
}else{
//Std animator
mContentParent.setVisibility(View.VISIBLE);
mExpandAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCard.setExpanded(true);
//Callback
if (mCard.getOnExpandAnimatorEndListener()!=null)
mCard.getOnExpandAnimatorEndListener().onExpandEnd(mCard);
}
});
mExpandAnimator.start();
}
}
/**
* Collapse animator
*/
private void animateCollapsing() {
if (getOnExpandListAnimatorListener()!=null){
//There is a List Animator.
getOnExpandListAnimatorListener().onCollapseStart(mCard.getCardView(), mContentParent);
}else{
//Std animator
int origHeight = mContentParent.getHeight();
ValueAnimator animator = createSlideAnimator(origHeight, 0);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
mContentParent.setVisibility(View.GONE);
mCard.setExpanded(false);
//Callback
if (mCard.getOnCollapseAnimatorEndListener()!=null)
mCard.getOnCollapseAnimatorEndListener().onCollapseEnd(mCard);
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
}
}
/**
* Create the Slide Animator invoked when the expand/collapse button is clicked
*/
protected ValueAnimator createSlideAnimator(int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = mInternalExpandLayout.getLayoutParams();
layoutParams.height = value;
mInternalExpandLayout.setLayoutParams(layoutParams);
}
});
return animator;
}
@Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld)
{
super.onSizeChanged(xNew, yNew, xOld, yOld);
mExpandedHeight = yNew;
}
// -------------------------------------------------------------
// OnExpandListAnimator Interface and Listener
// -------------------------------------------------------------
/**
* Interface to listen any callbacks when expand/collapse animation starts
*/
public interface OnExpandListAnimatorListener {
public void onExpandStart(CardView viewCard,View expandingLayout);
public void onCollapseStart(CardView viewCard,View expandingLayout);
}
/**
* Returns the listener invoked when expand/collpase animation starts
* It is used internally
*
* @return listener
*/
public OnExpandListAnimatorListener getOnExpandListAnimatorListener() {
return mOnExpandListAnimatorListener;
}
/**
* Sets the listener invoked when expand/collapse animation starts
* It is used internally. Don't override it.
*
* @param onExpandListAnimatorListener listener
*/
public void setOnExpandListAnimatorListener(OnExpandListAnimatorListener onExpandListAnimatorListener) {
this.mOnExpandListAnimatorListener = onExpandListAnimatorListener;
}
// -------------------------------------------------------------
// Bitmap export
// -------------------------------------------------------------
/**
* Create a {@link android.graphics.Bitmap} from CardView
* @return
*/
public Bitmap createBitmap(){
if (getWidth()<=0 && getHeight()<=0){
int spec = MeasureSpec.makeMeasureSpec( 0,MeasureSpec.UNSPECIFIED);
measure(spec,spec);
layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
Bitmap b = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
draw(c);
return b;
}
// -------------------------------------------------------------
// Getter and Setter
// -------------------------------------------------------------
/**
* Returns the view used by Expand Layout
*
* @return {@link View} used by Expand Layout
*/
public View getInternalExpandLayout() {
return mInternalExpandLayout;
}
public int getCollapsedHeight() {
return mCollapsedHeight;
}
public void setCollapsedHeight(int collapsedHeight) {
mCollapsedHeight = collapsedHeight;
}
public int getExpandedHeight() {
return mExpandedHeight;
}
public void setExpandedHeight(int expandedHeight) {
mExpandedHeight = expandedHeight;
}
/**
* Indicates if the card is expanded or collapsed
*
* @return <code>true</code> if the card is expanded
*/
public boolean isExpanded() {
if (mCard!=null){
return mCard.isExpanded();
}else
return false;
}
/**
* Sets the card as expanded or collapsed
*
* @param expanded <code>true</code> if the card is expanded
*/
public void setExpanded(boolean expanded) {
if (mCard!=null){
mCard.setExpanded(expanded);
}
}
/**
* Retrieves the InternalMainCardGlobalLayout.
* Background style is applied here.
*
* @return
*/
public View getInternalMainCardLayout() {
return mInternalMainCardLayout;
}
/**
* Changes dynamically the drawable resource to override the style of MainLayout.
*
* @param drawableResourceId drawable resource Id
*/
public void changeBackgroundResourceId(int drawableResourceId) {
if (drawableResourceId!=0){
if (mInternalMainCardLayout!=null){
mInternalMainCardLayout.setBackgroundResource(drawableResourceId);
}
}
}
/**
* Changes dynamically the drawable resource to override the style of MainLayout.
*
* @param drawableResource drawable resource
*/
@SuppressLint("NewApi")
public void changeBackgroundResource(Drawable drawableResource) {
if (drawableResource!=null){
if (mInternalMainCardLayout!=null){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
mInternalMainCardLayout.setBackground(drawableResource);
else
mInternalMainCardLayout.setBackgroundDrawable(drawableResource);
}
}
}
}
[/code]
修改完这三个类之后我们可以很方便的使用setUseLongClickSwipe来设置是否使用长按来触发SwipeToDismiss操作~
card_array_adapter = new CardArrayAdapter(this.getActivity(), list_todo_card);
card_array_adapter.setUseLongClickSwipe(true);
CardListView cardlistview = (CardListView)view.findViewById(R.id.cardlist);
cardlistview.setAdapter(card_array_adapter);
[/code]
转载于:https://my.oschina.net/gal/blog/200166
相关文章推荐
- 为cardslib添加长按滑动删除(Android)
- ANDROID 动态添加的listView,仿QQ滑动删除
- android 滑动删除的listview(自定义view)
- Android使用SwipeListView实现类似QQ的滑动删除效果
- Android – ListView 中添加按钮,动态删除添加ItemView的操作
- IOS--添加底部工具栏和UITableViewCell的滑动删除 推荐
- android 数据库 SQLiteOpenHelper和ContentProvider学习笔记---添加修改删除数据之联系人(二)
- Android界面实现 整合了刷新、加载更多、滑动删除功能的XListview
- android之RecycleView之ItemTouchHelper 处理拖拽、滑动删除
- Android的TextView中文字添加删除线,下划线
- Android——滑动监听RecyclerView线性流+左右划删除+上下移动
- 基于android的网络音乐播放器-添加viewpager和fragment实现滑动切换多个界面(二)
- android Item 滑动删除核心逻辑实现
- Android App中ListView仿QQ实现滑动删除效果的要点解析
- android listview滑动删除,实现item的点击缩回
- 【Android界面实现】整合了刷新、加载更多、滑动删除功能的XListview
- Android GridView扩展仿微信微博发图动态添加删除图片功能
- Android 对TextView添加删除线,下划线,加粗,斜体等效果
- Android 4.3 系统裁剪——删除不使用的app及添加自己app
- Android APP: BlackContact 添加/更新/删除名单信息