Android实现仿支付宝流水详解手机开发

今天给大家讲的是如何自定义下拉的ListView实现支付宝账单的效果,月份是需要悬浮的,然后没一个月归为一类,先看一个效果图吧。

这里写图片描述

场景:后台下发的数据就是一个List<对象>,考虑到实际情况,还需要做下拉的分页操作,所以,基于上面的情况,我们需要自定义一个可以拦截月份的view。

先定义一个FooterView类。

public class ListViewFooter extends LinearLayout { 
 
    public final static int STATE_NORMAL = 0; 
    public final static int STATE_READY = 1; 
    public final static int STATE_LOADING = 2; 
    private Context mContext; 
    private View mContentView; 
    private View mProgressBar; 
    private TextView mFootTextView; 
 
    public ListViewFooter(Context context) { 
        super(context); 
        initView(context); 
    } 
    public ListViewFooter(Context context, AttributeSet attrs) { 
        super(context, attrs); 
        initView(context); 
    } 
 
    //下拉状态处理 
    public void setState(int state) { 
        mFootTextView.setVisibility(View.INVISIBLE); 
        mProgressBar.setVisibility(View.GONE); 
        if (state == STATE_READY) { 
            mFootTextView.setVisibility(View.VISIBLE); 
            mProgressBar.setVisibility(View.GONE); 
            mFootTextView.setText(R.string.pull_footer_hint_ready); 
        } else if (state == STATE_LOADING) { 
            mProgressBar.setVisibility(View.VISIBLE); 
            mFootTextView.setVisibility(View.VISIBLE); 
        } else { 
            mFootTextView.setVisibility(View.VISIBLE); 
            mProgressBar.setVisibility(View.GONE); 
            mFootTextView.setText(R.string.pull_footer_hint_normal); 
        } 
    } 
 
    public void setBottomMargin(int height) { 
        if (height < 0) return ; 
        LayoutParams lp = (LayoutParams)mContentView.getLayoutParams(); 
        lp.bottomMargin = height; 
        mContentView.setLayoutParams(lp); 
    } 
 
    public int getBottomMargin() { 
        LayoutParams lp = (LayoutParams)mContentView.getLayoutParams(); 
        return lp.bottomMargin; 
    } 
 
    public void normal() { 
        mFootTextView.setVisibility(View.VISIBLE); 
        mProgressBar.setVisibility(View.GONE); 
    } 
 
 
    public void loading() { 
        mFootTextView.setVisibility(View.GONE); 
        mProgressBar.setVisibility(View.VISIBLE); 
    } 
 
    public void hide() { 
        LayoutParams lp = (LayoutParams)mContentView.getLayoutParams(); 
        lp.height = 0; 
        mContentView.setLayoutParams(lp); 
    } 
 
 
    public void show() { 
        LayoutParams lp = (LayoutParams)mContentView.getLayoutParams(); 
        lp.height = LayoutParams.WRAP_CONTENT; 
        mContentView.setLayoutParams(lp); 
    } 
 
    private void initView(Context context) { 
        mContext = context; 
        LinearLayout moreView = (LinearLayout) LayoutInflater.from(mContext).inflate(R.layout.layout_listview_footer, null); 
        addView(moreView); 
        moreView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); 
 
        mContentView = moreView.findViewById(R.id.footer_view); 
        mProgressBar = moreView.findViewById(R.id.footer_progressbar); 
        mFootTextView = (TextView)moreView.findViewById(R.id.footer_textview); 
    } 
    public TextView getmFootTextView() { 
        return mFootTextView; 
    } 
}

然后我们通过继承自ListViwe实现一个下拉的PullListView类。

public class PullFreshListView extends ListView implements OnScrollListener { 
private float mLastY = -1; 
private Scroller mScroller; 
private OnScrollListener mScrollListener; 
private ListViewPlusListener mListViewListener; 
private boolean mPullRefreshing = false; 
private ListViewFooter mFooterView; 
private boolean mEnablePullLoad; 
private boolean mPullLoading; 
private boolean mIsFooterReady = false; 
private int mTotalItemCount; 
private int mScrollBack; 
private int minItemCount=3; 
private final static int SCROLLBACK_HEADER = 0; 
private final static int SCROLLBACK_FOOTER = 1; 
private final static int SCROLL_DURATION = 400; 
private final static int PULL_LOAD_MORE_DELTA = 50; 
private final static float OFFSET_RADIO = 1.8f; 
public PullFreshListView(Context context) { 
super(context); 
initWithContext(context); 
} 
public PullFreshListView(Context context, AttributeSet attrs) { 
super(context, attrs); 
initWithContext(context); 
} 
public PullFreshListView(Context context, AttributeSet attrs, int defStyle) { 
super(context, attrs, defStyle); 
initWithContext(context); 
} 
private void initWithContext(Context context) { 
mScroller = new Scroller(context, new DecelerateInterpolator()); 
super.setOnScrollListener(this); 
mFooterView = new ListViewFooter(context); 
} 
@Override 
public void setAdapter(ListAdapter adapter) { 
if (mIsFooterReady == false) { 
mIsFooterReady = true; 
addFooterView(mFooterView); 
} 
super.setAdapter(adapter); 
} 
public void setLoadEnable(boolean enable) { 
mEnablePullLoad = enable; 
if (!mEnablePullLoad) { 
mFooterView.hide(); 
mFooterView.setOnClickListener(null); 
setFooterDividersEnabled(false); 
} else { 
mPullLoading = false; 
mFooterView.show(); 
mFooterView.setState(ListViewFooter.STATE_NORMAL); 
setFooterDividersEnabled(true); 
mFooterView.setOnClickListener(new OnClickListener() { 
@Override 
public void onClick(View v) { 
startLoadMore(); 
} 
}); 
} 
} 
public void stopLoadMore() { 
if (mPullLoading == true) { 
mPullLoading = false; 
mFooterView.setState(ListViewFooter.STATE_NORMAL); 
} 
} 
private void invokeOnScrolling() { 
if (mScrollListener instanceof OnXScrollListener) { 
OnXScrollListener l = (OnXScrollListener) mScrollListener; 
l.onXScrolling(this); 
} 
} 
private void resetHeaderHeight() { 
invalidate(); 
} 
private void updateFooterHeight(float delta) { 
int height = mFooterView.getBottomMargin() + (int) delta; 
if (mEnablePullLoad && !mPullLoading) { 
if (height > PULL_LOAD_MORE_DELTA) { 
mFooterView.setState(ListViewFooter.STATE_READY); 
} else { 
mFooterView.setState(ListViewFooter.STATE_NORMAL); 
} 
} 
mFooterView.setBottomMargin(height); 
} 
private void resetFooterHeight() { 
int bottomMargin = mFooterView.getBottomMargin(); 
if (bottomMargin > 0) { 
mScrollBack = SCROLLBACK_FOOTER; 
mScroller.startScroll(0, bottomMargin, 0, -bottomMargin, 
SCROLL_DURATION); 
invalidate(); 
} 
} 
private void startLoadMore() { 
mPullLoading = true; 
mFooterView.setState(ListViewFooter.STATE_LOADING); 
if (mListViewListener != null) { 
mListViewListener.onLoadMore(); 
} 
} 
@Override 
public boolean onTouchEvent(MotionEvent ev) { 
if (mLastY == -1) { 
mLastY = ev.getRawY(); 
} 
switch (ev.getAction()) { 
case MotionEvent.ACTION_DOWN: 
mLastY = ev.getRawY(); 
break; 
case MotionEvent.ACTION_MOVE: 
final float deltaY = ev.getRawY() - mLastY; 
mLastY = ev.getRawY(); 
if (getLastVisiblePosition() == mTotalItemCount - 1 
&& (mFooterView.getBottomMargin() > 0 || deltaY < 0)) { 
updateFooterHeight(-deltaY / OFFSET_RADIO); 
} 
break; 
default: 
mLastY = -1; 
if (getFirstVisiblePosition() == 0) { 
resetHeaderHeight(); 
} else if (getLastVisiblePosition() == mTotalItemCount - 1) { 
if (mEnablePullLoad 
&& mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA 
&& !mPullLoading) { 
startLoadMore(); 
} 
resetFooterHeight(); 
} 
break; 
} 
return super.onTouchEvent(ev); 
} 
@Override 
public void computeScroll() { 
if (mScroller.computeScrollOffset()) { 
if (mScrollBack == SCROLLBACK_HEADER) { 
} else { 
mFooterView.setBottomMargin(mScroller.getCurrY()); 
} 
postInvalidate(); 
invokeOnScrolling(); 
} 
super.computeScroll(); 
} 
@Override 
public void setOnScrollListener(OnScrollListener l) { 
mScrollListener = l; 
} 
@Override 
public void onScrollStateChanged(AbsListView view, int scrollState) { 
if (mScrollListener != null) { 
mScrollListener.onScrollStateChanged(view, scrollState); 
} 
} 
@Override 
public void onScroll(AbsListView view, int firstVisibleItem, 
int visibleItemCount, int totalItemCount) { 
if(mFooterView!=null){ 
if (totalItemCount<minItemCount+2) { 
mFooterView.getmFootTextView().setText(""); 
}else { 
if(mFooterView.getmFootTextView().getText().toString().equals(""))mFooterView.getmFootTextView(). 
setText(getResources().getString(R.string.pull_footer_hint_normal)); 
} 
} 
mTotalItemCount = totalItemCount; 
if (mScrollListener != null) { 
mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, 
totalItemCount); 
} 
} 
public void setListViewPlusListener(ListViewPlusListener l) { 
mListViewListener = l; 
} 
public interface OnXScrollListener extends OnScrollListener { 
void onXScrolling(View view); 
} 
public interface ListViewPlusListener { 
void onLoadMore(); 
} 
}

最后要实现可以悬浮的效果,我们需要对布局的滑动的数据进行监听,具体请看代码:

public class PullStickyListView extends PullFreshListView { 
public  interface PinnedSectionListAdapter extends ListAdapter { 
boolean isItemViewTypePinned(int viewType); 
} 
static class PinnedSection { 
public View view; 
public int position; 
public long id; 
} 
private final Rect mTouchRect = new Rect(); 
private final PointF mTouchPoint = new PointF(); 
private int mTouchSlop; 
private View mTouchTarget; 
private MotionEvent mDownEvent; 
private GradientDrawable mShadowDrawable; 
private int mSectionsDistanceY; 
private int mShadowHeight; 
private OnScrollListener mDelegateOnScrollListener; 
private PinnedSection mRecycleSection; 
private PinnedSection mPinnedSection; 
private int mTranslateY; 
private final OnScrollListener mOnScrollListener = new OnScrollListener() { 
@Override 
public void onScrollStateChanged(AbsListView view, int scrollState) { 
if (mDelegateOnScrollListener != null) { 
mDelegateOnScrollListener.onScrollStateChanged(view, scrollState); 
} 
} 
@Override 
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 
if (mDelegateOnScrollListener != null) { 
mDelegateOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); 
} 
ListAdapter adapter = getAdapter(); 
if (adapter == null || visibleItemCount == 0) return; 
final boolean isFirstVisibleItemSection = 
isItemViewTypePinned(adapter, adapter.getItemViewType(firstVisibleItem)); 
if (isFirstVisibleItemSection) { 
View sectionView = getChildAt(0); 
if (sectionView.getTop() == getPaddingTop()) { 
destroyPinnedShadow(); 
} else { 
ensureShadowForPosition(firstVisibleItem, firstVisibleItem, visibleItemCount); 
} 
} else { 
int sectionPosition = findCurrentSectionPosition(firstVisibleItem); 
if (sectionPosition > -1) { 
ensureShadowForPosition(sectionPosition, firstVisibleItem, visibleItemCount); 
} else { 
destroyPinnedShadow(); 
} 
} 
} 
}; 
private final DataSetObserver mDataSetObserver = new DataSetObserver() { 
@Override 
public void onChanged() { 
recreatePinnedShadow(); 
} 
@Override 
public void onInvalidated() { 
recreatePinnedShadow(); 
} 
}; 
public PullStickyListView(Context context, AttributeSet attrs) { 
super(context, attrs); 
initView(); 
} 
public PullStickyListView(Context context, AttributeSet attrs, int defStyle) { 
super(context, attrs, defStyle); 
initView(); 
} 
private void initView() { 
setOnScrollListener(mOnScrollListener); 
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 
//        initShadow(true); 
} 
public void setShadowVisible(boolean visible) { 
initShadow(visible); 
if (mPinnedSection != null) { 
View v = mPinnedSection.view; 
invalidate(v.getLeft(), v.getTop(), v.getRight(), v.getBottom() + mShadowHeight); 
} 
} 
public void initShadow(boolean visible) { 
if (visible) { 
if (mShadowDrawable == null) { 
mShadowDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, 
new int[]{Color.parseColor("#ffa0a0a0"), Color.parseColor("#50a0a0a0"), Color.parseColor("#00a0a0a0")}); 
mShadowHeight = (int) (8 * getResources().getDisplayMetrics().density); 
} 
} else { 
if (mShadowDrawable != null) { 
mShadowDrawable = null; 
mShadowHeight = 0; 
} 
} 
} 
void createPinnedShadow(int position) { 
PinnedSection pinnedShadow = mRecycleSection; 
mRecycleSection = null; 
if (pinnedShadow == null) pinnedShadow = new PinnedSection(); 
View pinnedView = getAdapter().getView(position, pinnedShadow.view, PullStickyListView.this); 
LayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams(); 
if (layoutParams == null) { 
layoutParams = (LayoutParams) generateDefaultLayoutParams(); 
pinnedView.setLayoutParams(layoutParams); 
} 
int heightMode = MeasureSpec.getMode(layoutParams.height); 
int heightSize = MeasureSpec.getSize(layoutParams.height); 
if (heightMode == MeasureSpec.UNSPECIFIED) heightMode = MeasureSpec.EXACTLY; 
int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom(); 
if (heightSize > maxHeight) heightSize = maxHeight; 
// measure & layout 
int ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY); 
int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode); 
pinnedView.measure(ws, hs); 
pinnedView.layout(0, 0, pinnedView.getMeasuredWidth(), pinnedView.getMeasuredHeight()); 
mTranslateY = 0; 
// initialize pinned shadow 
pinnedShadow.view = pinnedView; 
pinnedShadow.position = position; 
pinnedShadow.id = getAdapter().getItemId(position); 
// store pinned shadow 
mPinnedSection = pinnedShadow; 
} 
void destroyPinnedShadow() { 
if (mPinnedSection != null) { 
mRecycleSection = mPinnedSection; 
mPinnedSection = null; 
} 
} 
void ensureShadowForPosition(int sectionPosition, int firstVisibleItem, int visibleItemCount) { 
if (visibleItemCount < 2) { 
destroyPinnedShadow(); 
return; 
} 
if (mPinnedSection != null 
&& mPinnedSection.position != sectionPosition) { 
destroyPinnedShadow(); 
} 
if (mPinnedSection == null) { 
createPinnedShadow(sectionPosition); 
} 
int nextPosition = sectionPosition + 1; 
if (nextPosition < getCount()) { 
int nextSectionPosition = findFirstVisibleSectionPosition(nextPosition, 
visibleItemCount - (nextPosition - firstVisibleItem)); 
if (nextSectionPosition > -1) { 
View nextSectionView = getChildAt(nextSectionPosition - firstVisibleItem); 
final int bottom = mPinnedSection.view.getBottom() + getPaddingTop(); 
mSectionsDistanceY = nextSectionView.getTop() - bottom; 
if (mSectionsDistanceY < 0) { 
mTranslateY = mSectionsDistanceY; 
} else { 
mTranslateY = 0; 
} 
} else { 
mTranslateY = 0; 
mSectionsDistanceY = Integer.MAX_VALUE; 
} 
} 
} 
int findFirstVisibleSectionPosition(int firstVisibleItem, int visibleItemCount) { 
ListAdapter adapter = getAdapter(); 
int adapterDataCount = adapter.getCount(); 
if (getLastVisiblePosition() >= adapterDataCount) 
return -1; 
if (firstVisibleItem + visibleItemCount >= adapterDataCount) {//added to prevent index Outofbound (in case) 
visibleItemCount = adapterDataCount - firstVisibleItem; 
} 
for (int childIndex = 0; childIndex < visibleItemCount; childIndex++) { 
int position = firstVisibleItem + childIndex; 
int viewType = adapter.getItemViewType(position); 
if (isItemViewTypePinned(adapter, viewType)) return position; 
} 
return -1; 
} 
int findCurrentSectionPosition(int fromPosition) { 
ListAdapter adapter = getAdapter(); 
if (fromPosition >= adapter.getCount()) return -1; 
if (adapter instanceof SectionIndexer) { 
SectionIndexer indexer = (SectionIndexer) adapter; 
int sectionPosition = indexer.getSectionForPosition(fromPosition); 
int itemPosition = indexer.getPositionForSection(sectionPosition); 
int typeView = adapter.getItemViewType(itemPosition); 
if (isItemViewTypePinned(adapter, typeView)) { 
return itemPosition; 
} 
} 
for (int position = fromPosition; position >= 0; position--) { 
int viewType = adapter.getItemViewType(position); 
if (isItemViewTypePinned(adapter, viewType)) return position; 
} 
return -1; 
} 
void recreatePinnedShadow() { 
destroyPinnedShadow(); 
ListAdapter adapter = getAdapter(); 
if (adapter != null && adapter.getCount() > 0) { 
int firstVisiblePosition = getFirstVisiblePosition(); 
int sectionPosition = findCurrentSectionPosition(firstVisiblePosition); 
if (sectionPosition == -1) return; 
ensureShadowForPosition(sectionPosition, 
firstVisiblePosition, getLastVisiblePosition() - firstVisiblePosition); 
} 
} 
@Override 
public void setOnScrollListener(OnScrollListener listener) { 
if (listener == mOnScrollListener) { 
super.setOnScrollListener(listener); 
} else { 
mDelegateOnScrollListener = listener; 
} 
} 
@Override 
public void onRestoreInstanceState(Parcelable state) { 
super.onRestoreInstanceState(state); 
post(new Runnable() { 
@Override 
public void run() { 
recreatePinnedShadow(); 
} 
}); 
} 
@Override 
public void setAdapter(ListAdapter adapter) { 
if (adapter != null) { 
if (!(adapter instanceof PinnedSectionListAdapter)) 
throw new IllegalArgumentException("Does your adapter implement PinnedSectionListAdapter?"); 
if (adapter.getViewTypeCount() < 2) 
throw new IllegalArgumentException("Does your adapter handle at least two types" + 
" of views in getViewTypeCount() method: items and sections?"); 
} 
ListAdapter oldAdapter = getAdapter(); 
if (oldAdapter != null) oldAdapter.unregisterDataSetObserver(mDataSetObserver); 
if (adapter != null) adapter.registerDataSetObserver(mDataSetObserver); 
if (oldAdapter != adapter) destroyPinnedShadow(); 
super.setAdapter(adapter); 
} 
@Override 
protected void onLayout(boolean changed, int l, int t, int r, int b) { 
super.onLayout(changed, l, t, r, b); 
if (mPinnedSection != null) { 
int parentWidth = r - l - getPaddingLeft() - getPaddingRight(); 
int shadowWidth = mPinnedSection.view.getWidth(); 
if (parentWidth != shadowWidth) { 
recreatePinnedShadow(); 
} 
} 
} 
@Override 
protected void dispatchDraw(Canvas canvas) { 
super.dispatchDraw(canvas); 
if (mPinnedSection != null) { 
int pLeft = getListPaddingLeft(); 
int pTop = getListPaddingTop(); 
View view = mPinnedSection.view; 
canvas.save(); 
int clipHeight = view.getHeight() + 
(mShadowDrawable == null ? 0 : Math.min(mShadowHeight, mSectionsDistanceY)); 
canvas.clipRect(pLeft, pTop, pLeft + view.getWidth(), pTop + clipHeight); 
canvas.translate(pLeft, pTop + mTranslateY); 
drawChild(canvas, mPinnedSection.view, getDrawingTime()); 
if (mShadowDrawable != null && mSectionsDistanceY > 0) { 
mShadowDrawable.setBounds(mPinnedSection.view.getLeft(), 
mPinnedSection.view.getBottom(), 
mPinnedSection.view.getRight(), 
mPinnedSection.view.getBottom() + mShadowHeight); 
mShadowDrawable.draw(canvas); 
} 
canvas.restore(); 
} 
} 
@Override 
public boolean dispatchTouchEvent(MotionEvent ev) { 
final float x = ev.getX(); 
final float y = ev.getY(); 
final int action = ev.getAction(); 
if (action == MotionEvent.ACTION_DOWN 
&& mTouchTarget == null 
&& mPinnedSection != null 
&& isPinnedViewTouched(mPinnedSection.view, x, y)) { 
mTouchTarget = mPinnedSection.view; 
mTouchPoint.x = x; 
mTouchPoint.y = y; 
mDownEvent = MotionEvent.obtain(ev); 
} 
if (mTouchTarget != null) { 
if (isPinnedViewTouched(mTouchTarget, x, y)) { 
mTouchTarget.dispatchTouchEvent(ev); 
} 
if (action == MotionEvent.ACTION_UP) { 
super.dispatchTouchEvent(ev); 
performPinnedItemClick(); 
clearTouchTarget(); 
} else if (action == MotionEvent.ACTION_CANCEL) { 
clearTouchTarget(); 
} else if (action == MotionEvent.ACTION_MOVE) { 
if (Math.abs(y - mTouchPoint.y) > mTouchSlop) { 
MotionEvent event = MotionEvent.obtain(ev); 
event.setAction(MotionEvent.ACTION_CANCEL); 
mTouchTarget.dispatchTouchEvent(event); 
event.recycle(); 
super.dispatchTouchEvent(mDownEvent); 
super.dispatchTouchEvent(ev); 
clearTouchTarget(); 
} 
} 
return true; 
} 
return super.dispatchTouchEvent(ev); 
} 
private boolean isPinnedViewTouched(View view, float x, float y) { 
view.getHitRect(mTouchRect); 
mTouchRect.top += mTranslateY; 
mTouchRect.bottom += mTranslateY + getPaddingTop(); 
mTouchRect.left += getPaddingLeft(); 
mTouchRect.right -= getPaddingRight(); 
return mTouchRect.contains((int) x, (int) y); 
} 
private void clearTouchTarget() { 
mTouchTarget = null; 
if (mDownEvent != null) { 
mDownEvent.recycle(); 
mDownEvent = null; 
} 
} 
private boolean performPinnedItemClick() { 
if (mPinnedSection == null) return false; 
OnItemClickListener listener = getOnItemClickListener(); 
if (listener != null && getAdapter().isEnabled(mPinnedSection.position)) { 
View view = mPinnedSection.view; 
playSoundEffect(SoundEffectConstants.CLICK); 
if (view != null) { 
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 
} 
listener.onItemClick(this, view, mPinnedSection.position, mPinnedSection.id); 
return true; 
} 
return false; 
} 
public static boolean isItemViewTypePinned(ListAdapter adapter, int viewType) { 
if (adapter instanceof HeaderViewListAdapter) { 
adapter = ((HeaderViewListAdapter) adapter).getWrappedAdapter(); 
} 
return ((PinnedSectionListAdapter) adapter).isItemViewTypePinned(viewType); 
} 
}

附:仿支付宝账单流水
饼状图:http://blog.csdn.net/kuangxiaoguo0123/article/details/53282809

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/5661.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论