/* * This is the source code of Telegram for Android v. 1.3.2. * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * * Copyright Nikolai Kudashov, 2013. */ package com.yahala.ui.Views; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.HeaderViewListAdapter; import android.widget.ListAdapter; import android.widget.ListView; public class PinnedHeaderListView extends ListView implements OnScrollListener, View.OnTouchListener { public int exHeaderRightPadding = 0; private OnScrollListener mOnScrollListener; private PinnedSectionedHeaderAdapter mAdapter; private OnTouchListener mForwardingTouchListener = null; private float mLastUpEventY = -1; private View mCurrentHeader; private int mCurrentHeaderViewType = 0; private float mHeaderOffset; private boolean mShouldPin = true; private int mCurrentSection = 0; private int mWidthMode; public PinnedHeaderListView(Context context) { super(context); super.setOnScrollListener(this); super.setOnTouchListener(this); } public PinnedHeaderListView(Context context, AttributeSet attrs) { super(context, attrs); super.setOnScrollListener(this); super.setOnTouchListener(this); } public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); super.setOnScrollListener(this); super.setOnTouchListener(this); } public void setPinHeaders(boolean shouldPin) { mShouldPin = shouldPin; } @Override public void setAdapter(ListAdapter adapter) { mCurrentHeader = null; if (adapter instanceof PinnedSectionedHeaderAdapter) { mAdapter = (PinnedSectionedHeaderAdapter) adapter; } else { mAdapter = null; } super.setAdapter(adapter); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (mAdapter == null) { return; } if (mOnScrollListener != null) { mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } if (mAdapter == null || mAdapter.getCount() == 0 || !mShouldPin || (firstVisibleItem < getHeaderViewsCount())) { mCurrentHeader = null; mHeaderOffset = 0.0f; for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) { View header = getChildAt(i); if (header != null) { header.setVisibility(VISIBLE); } } return; } firstVisibleItem -= getHeaderViewsCount(); int section = mAdapter.getSectionForPosition(firstVisibleItem); int viewType = mAdapter.getSectionHeaderViewType(section); mCurrentHeader = getSectionHeaderView(section, mCurrentHeaderViewType != viewType ? null : mCurrentHeader); if (mCurrentHeader != null && mCurrentHeader.getPaddingLeft() != getPaddingLeft()) { mCurrentHeader.setPadding(getPaddingLeft(), mCurrentHeader.getPaddingTop(), getPaddingRight() + (int) (getResources().getDisplayMetrics().density * exHeaderRightPadding), 0); } ensurePinnedHeaderLayout(mCurrentHeader, false); mCurrentHeaderViewType = viewType; mHeaderOffset = 0.0f; for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) { if (mAdapter.isSectionHeader(i)) { View header = getChildAt(i - firstVisibleItem); float headerTop = header.getTop(); float pinnedHeaderHeight = mCurrentHeader.getMeasuredHeight(); header.setVisibility(VISIBLE); if (pinnedHeaderHeight >= headerTop && headerTop > -1) { mHeaderOffset = headerTop - header.getHeight(); } else if (headerTop <= 0) { header.setVisibility(INVISIBLE); } } } invalidate(); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (mAdapter == null) { return; } if (mOnScrollListener != null) { mOnScrollListener.onScrollStateChanged(view, scrollState); } } private View getSectionHeaderView(int section, View oldView) { boolean shouldLayout = section != mCurrentSection || oldView == null; View view = mAdapter.getSectionHeaderView(section, oldView, this); if (shouldLayout) { // a new section, thus a new header. We should lay it out again ensurePinnedHeaderLayout(view, false); mCurrentSection = section; } return view; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mAdapter == null) { return; } if (mCurrentHeader != null) { ensurePinnedHeaderLayout(mCurrentHeader, true); } } private void ensurePinnedHeaderLayout(View header, boolean forceLayout) { if (header.isLayoutRequested() || forceLayout) { int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), mWidthMode); int heightSpec; ViewGroup.LayoutParams layoutParams = header.getLayoutParams(); if (layoutParams != null && layoutParams.height > 0) { heightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY); } else { heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } header.measure(widthSpec, heightSpec); header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight()); } } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (mAdapter == null || !mShouldPin || mCurrentHeader == null) { return; } int saveCount = canvas.save(); canvas.translate(0, mHeaderOffset); canvas.clipRect(0, 0, getWidth(), mCurrentHeader.getMeasuredHeight()); // needed for < HONEYCOMB mCurrentHeader.draw(canvas); canvas.restoreToCount(saveCount); } @Override public boolean performItemClick(View view, int position, long id) { if (mAdapter != null && mLastUpEventY > 0 && mCurrentHeader != null && mLastUpEventY < mCurrentHeader.getBottom()) { mCurrentHeader.performClick(); mLastUpEventY = -1; return true; } return super.performItemClick(view, position, id); } @Override public void setOnTouchListener(OnTouchListener l) { mForwardingTouchListener = l; } @Override public boolean onTouch(View v, MotionEvent event) { if (mForwardingTouchListener != null) { mForwardingTouchListener.onTouch(v, event); } if (mCurrentHeader != null && event.getY() < mCurrentHeader.getHeight()) { if (event.getAction() == MotionEvent.ACTION_UP) { mLastUpEventY = event.getY(); } } return false; } @Override public void setOnScrollListener(OnScrollListener l) { mOnScrollListener = l; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWidthMode = MeasureSpec.getMode(widthMeasureSpec); } public void setOnItemClickListener(PinnedHeaderListView.OnItemClickListener listener) { super.setOnItemClickListener(listener); } public static interface PinnedSectionedHeaderAdapter { public boolean isSectionHeader(int position); public int getSectionForPosition(int position); public View getSectionHeaderView(int section, View convertView, ViewGroup parent); public int getSectionHeaderViewType(int section); public int getCount(); } public static abstract class OnItemClickListener implements AdapterView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> adapterView, View view, int rawPosition, long id) { SectionedBaseAdapter adapter; if (adapterView.getAdapter() instanceof HeaderViewListAdapter) { HeaderViewListAdapter wrapperAdapter = (HeaderViewListAdapter) adapterView.getAdapter(); adapter = (SectionedBaseAdapter) wrapperAdapter.getWrappedAdapter(); } else { adapter = (SectionedBaseAdapter) adapterView.getAdapter(); } int section = adapter.getSectionForPosition(rawPosition); int position = adapter.getPositionInSectionForPosition(rawPosition); if (position == -1) { onSectionClick(adapterView, view, section, id); } else { onItemClick(adapterView, view, section, position, id); } } public abstract void onItemClick(AdapterView<?> adapterView, View view, int section, int position, long id); public abstract void onSectionClick(AdapterView<?> adapterView, View view, int section, long id); } }