开发中经常需要写一个带tab切换并且tab有吸顶效果而且头要渐变效果的详情页,如下图:
![
之前做项目时,一个人需要维护三四种这样类似的页面,每次在scrollView滚动时做一些列的计算都很痛苦,各种计算吸顶以及滚动时tab选中的位置。后来就萌生了一个想法,直接写一个基类,在基类里面完成完成这一系列的计算,然后子类直接继承基类,实现几个画页面的方法就好了!下面直接上代码:
基类activity:
private View mActionBarView;
private LinearLayout mContainerLayout;
private ObservableScrollView mScrollView;
private CommonDetailTabLayout mTabLayout1;
private CommonDetailTabLayout mTabLayout2;
protected ViewGroup mBottomContainer;
private int mStatusBarHeight = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.lesson_detail_layout);
initView();
}
private void initView() {
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
mStatusBarHeight = getResources().getDimensionPixelSize(resourceId);
initTabLayout();
mActionBarView = getView(R.id.ll_actionbar);
mActionBarView.getBackground().setAlpha(0);
mScrollView = getView(R.id.sv_content);
mScrollView.addScrollViewCallbacks(mCallBacks);
mContainerLayout = getView(R.id.ll_container);
if (getHeaderView() != null) {
mContainerLayout.addView(getHeaderView());
}
mContainerLayout.addView(mTabLayout2);
for (View tabView : getTabViews()) {
if (tabView != null) {
mContainerLayout.addView(tabView);
}
}
mBottomContainer = getView(R.id.ll_bottom_container);
if (getBottomView() == null) {
mBottomContainer.setVisibility(View.GONE);
} else {
mBottomContainer.setVisibility(View.VISIBLE);
}
}
protected abstract View getHeaderImageView();
protected abstract View getHeaderView();
protected abstract View getBottomView();
protected abstract ArrayList getTabViews();
protected void setTabData(String... data) {
ArrayList tabData = new ArrayList<>();
int length = data.length;
if (length != getTabViews().size()) {
return;
}
for (int i = 0; i < length; i++) {
CommonDetailTabLayout.TabObject tabObject = new CommonDetailTabLayout.TabObject();
tabObject.tabName = data[i];
final int finalI = i;
tabObject.listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
int height = getHeaderView().getMeasuredHeight() - DimenUtils.dip2px(mActivity,52);
for (int j = 0; j < finalI ; j ++){
height += getTabViews().get(j).getMeasuredHeight();
}
mTabLayout1.check(finalI);
mTabLayout2.check(finalI);
mScrollView.scrollVerticallyTo(height);
}
};
tabData.add(tabObject);
}
mTabLayout1.setData(tabData);
mTabLayout2.setData(tabData);
}
private void initTabLayout() {
mTabLayout1 = getView(R.id.tab);
mTabLayout1.setVisibility(View.GONE);
mTabLayout2 = new CommonDetailTabLayout(mActivity);
}
private ObservableScrollViewCallbacks mCallBacks = new ObservableScrollViewCallbacks() {
@Override
public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) {
//计算actionbar透明度
float progress = (scrollY * 1.0f)
/ (getHeaderImageView().getHeight() - DimenUtils.dip2px(mActivity,52));
progress = progress > 1 ? 1 : progress;
mActionBarView.getBackground().setAlpha((int) (255 * progress));
//滚动选中tab
int size = getTabViews().size();
for (int i = 0; i < size; i++) {
View view = getTabViews().get(i);
int top = view.getTop() - DimenUtils.dip2px(mActivity, 94);
int bottom = top + view.getMeasuredHeight();
if (scrollY > top && scrollY < bottom) {
mTabLayout1.check(i);
mTabLayout2.check(i);
break;
}
if (i == size - 2 && scrollY > top) {
mTabLayout1.check(size - 1);
mTabLayout2.check(size - 1);
}
}
//控制顶部tab的显示与隐藏
int headerHeight = DimenUtils.dip2px(mActivity, 52) + mStatusBarHeight;
int[] loc = new int[2];
mTabLayout2.getLocationOnScreen(loc);
mTabLayout1.setVisibility(loc[1] >= headerHeight ? View.GONE : View.VISIBLE);
}
@Override
public void onDownMotionEvent() {
}
@Override
public void onUpOrCancelMotionEvent(ScrollState scrollState) {
}
};
tab控件
public class CommonDetailTabLayout extends RadioGroup {
private Context mContext;
public ArrayList mList = new ArrayList();
public CommonDetailTabLayout(Context context) {
super(context);
mContext = context;
initConfig();
}
public CommonDetailTabLayout(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
mContext = context;
initConfig();
}
private void initConfig() {
setOrientation(HORIZONTAL);
setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, Utils.dip2px(mContext, 42)));
int padding = Utils.dip2px(mContext,10);
setBackgroundResource(R.drawable.bg_common_twoline);
setPadding(padding, 0, padding, 0);
}
public void setData(ArrayList list) {
if (list == null) {
return;
}
removeAllViews();
mList.clear();
int size = list.size();
for (int i = 0; i < size; i++) {
RadioButton radio = (RadioButton) LayoutInflater.from(mContext).inflate(R.layout.detai_radio_button, this, false);
radio.setId(i);
if (i == 0) {
radio.setChecked(true);
}
radio.setText(list.get(i).tabName);
radio.setOnClickListener(list.get(i).listener);
mList.add(radio);
addView(radio);
}
}
public static class TabObject {
public String tabName;
public OnClickListener listener;
}
public ArrayList getRadioList() {
return mList;
}
public void resetChecked() {
check(getChildAt(0).getId());
}
}
重写的scrollview
public class ObservableScrollView extends ScrollView implements ObserveScrollable {
// Fields that should be saved onSaveInstanceState
private int mPrevScrollY;
private int mScrollY;
// Fields that don't need to be saved onSaveInstanceState
private ObservableScrollViewCallbacks mCallbacks;
private List mCallbackCollection;
private ScrollState mScrollState;
private boolean mFirstScroll;
private boolean mDragging;
private boolean mIntercepted;
private MotionEvent mPrevMoveEvent;
private ViewGroup mTouchInterceptionViewGroup;
public ObservableScrollView(Context context) {
super(context);
}
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
mPrevScrollY = ss.prevScrollY;
mScrollY = ss.scrollY;
super.onRestoreInstanceState(ss.getSuperState());
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.prevScrollY = mPrevScrollY;
ss.scrollY = mScrollY;
return ss;
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (hasNoCallbacks()) {
return;
}
mScrollY = t;
dispatchOnScrollChanged(t, mFirstScroll, mDragging);
if (mFirstScroll) {
mFirstScroll = false;
}
if (mPrevScrollY < t) {
mScrollState = ScrollState.UP;
} else if (t < mPrevScrollY) {
mScrollState = ScrollState.DOWN;
//} else {
// Keep previous state while dragging.
// Never makes it STOP even if scrollY not changed.
// Before Android 4.4, onTouchEvent calls onScrollChanged directly for ACTION_MOVE,
// which makes mScrollState always STOP when onUpOrCancelMotionEvent is called.
// STOP state is now meaningless for ScrollView.
}
mPrevScrollY = t;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (hasNoCallbacks()) {
return super.onInterceptTouchEvent(ev);
}
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
// Whether or not motion events are consumed by children,
// flag initializations which are related to ACTION_DOWN events should be executed.
// Because if the ACTION_DOWN is consumed by children and only ACTION_MOVEs are
// passed to parent (this view), the flags will be invalid.
// Also, applications might implement initialization codes to onDownMotionEvent,
// so call it here.
mFirstScroll = mDragging = true;
dispatchOnDownMotionEvent();
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (hasNoCallbacks()) {
return super.onTouchEvent(ev);
}
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIntercepted = false;
mDragging = false;
dispatchOnUpOrCancelMotionEvent(mScrollState);
break;
case MotionEvent.ACTION_MOVE:
if (mPrevMoveEvent == null) {
mPrevMoveEvent = ev;
}
float diffY = ev.getY() - mPrevMoveEvent.getY();
mPrevMoveEvent = MotionEvent.obtainNoHistory(ev);
if (getCurrentScrollY() - diffY <= 0) {
// Can't scroll anymore.
if (mIntercepted) {
// Already dispatched ACTION_DOWN event to parents, so stop here.
return false;
}
// Apps can set the interception target other than the direct parent.
final ViewGroup parent;
if (mTouchInterceptionViewGroup == null) {
parent = (ViewGroup) getParent();
} else {
parent = mTouchInterceptionViewGroup;
}
// Get offset to parents. If the parent is not the direct parent,
// we should aggregate offsets from all of the parents.
float offsetX = 0;
float offsetY = 0;
for (View v = this; v != null && v != parent; v = (View) v.getParent()) {
offsetX += v.getLeft() - v.getScrollX();
offsetY += v.getTop() - v.getScrollY();
}
final MotionEvent event = MotionEvent.obtainNoHistory(ev);
event.offsetLocation(offsetX, offsetY);
if (parent.onInterceptTouchEvent(event)) {
mIntercepted = true;
// If the parent wants to intercept ACTION_MOVE events,
// we pass ACTION_DOWN event to the parent
// as if these touch events just have began now.
event.setAction(MotionEvent.ACTION_DOWN);
// Return this onTouchEvent() first and set ACTION_DOWN event for parent
// to the queue, to keep events sequence.
post(new Runnable() {
@Override
public void run() {
parent.dispatchTouchEvent(event);
}
});
return false;
}
// Even when this can't be scrolled anymore,
// simply returning false here may cause subView's click,
// so delegate it to super.
return super.onTouchEvent(ev);
}
break;
}
return super.onTouchEvent(ev);
}
@Override
public void setScrollViewCallbacks(ObservableScrollViewCallbacks listener) {
mCallbacks = listener;
}
@Override
public void addScrollViewCallbacks(ObservableScrollViewCallbacks listener) {
if (mCallbackCollection == null) {
mCallbackCollection = new ArrayList();
}
mCallbackCollection.add(listener);
}
@Override
public void removeScrollViewCallbacks(ObservableScrollViewCallbacks listener) {
if (mCallbackCollection != null) {
mCallbackCollection.remove(listener);
}
}
@Override
public void clearScrollViewCallbacks() {
if (mCallbackCollection != null) {
mCallbackCollection.clear();
}
}
@Override
public void setTouchInterceptionViewGroup(ViewGroup viewGroup) {
mTouchInterceptionViewGroup = viewGroup;
}
@Override
public void scrollVerticallyTo(int y) {
scrollTo(0, y);
}
@Override
public int getCurrentScrollY() {
return mScrollY;
}
private void dispatchOnDownMotionEvent() {
if (mCallbacks != null) {
mCallbacks.onDownMotionEvent();
}
if (mCallbackCollection != null) {
for (int i = 0; i < mCallbackCollection.size(); i++) {
ObservableScrollViewCallbacks callbacks = mCallbackCollection.get(i);
callbacks.onDownMotionEvent();
}
}
}
private void dispatchOnScrollChanged(int scrollY, boolean firstScroll, boolean dragging) {
if (mCallbacks != null) {
mCallbacks.onScrollChanged(scrollY, firstScroll, dragging);
}
if (mCallbackCollection != null) {
for (int i = 0; i < mCallbackCollection.size(); i++) {
ObservableScrollViewCallbacks callbacks = mCallbackCollection.get(i);
callbacks.onScrollChanged(scrollY, firstScroll, dragging);
}
}
}
private void dispatchOnUpOrCancelMotionEvent(ScrollState scrollState) {
if (mCallbacks != null) {
mCallbacks.onUpOrCancelMotionEvent(scrollState);
}
if (mCallbackCollection != null) {
for (int i = 0; i < mCallbackCollection.size(); i++) {
ObservableScrollViewCallbacks callbacks = mCallbackCollection.get(i);
callbacks.onUpOrCancelMotionEvent(scrollState);
}
}
}
private boolean hasNoCallbacks() {
return mCallbacks == null && mCallbackCollection == null;
}
static class SavedState extends BaseSavedState {
int prevScrollY;
int scrollY;
/**
* Called by onSaveInstanceState.
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Called by CREATOR.
*/
private SavedState(Parcel in) {
super(in);
prevScrollY = in.readInt();
scrollY = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(prevScrollY);
out.writeInt(scrollY);
}
public static final Creator CREATOR
= new Creator() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}
基类activity的layout
下面是我自己继承基类写的一个小demo
public class DepositDetailActivity extends DetailBaseActivity {
private DetailHeaderWidget mHeaderWidget;
@Override
protected void onCreate(Bundle savedInstanceState) {
mHeaderWidget = new DetailHeaderWidget(this);
super.onCreate(savedInstanceState);
setTitle("课程详情");
setTabData("1111","2222","3333");
}
@Override
protected View getHeaderImageView() {
return mHeaderWidget.getTopImage();
}
@Override
protected View getHeaderView() {
return mHeaderWidget.getView();
}
@Override
protected ArrayList getTabViews() {
ArrayList list = new ArrayList<>();
for (int i = 0 ; i < 3 ; i++){
TextView textView = new TextView(mActivity);
textView.setText("第" + i);
textView.setGravity(Gravity.CENTER);
textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, Utils.dip2px(mActivity,i == 2 ? 800 : 800)));
list.add(textView);
}
return list;
}
}
效果如图:
![LZ2MUZ(SD}BAN[RY
J$]FX.png](http://upload-images.jianshu.io/upload_images/4266338-636ae9402a5cef05.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![]TKL434$JD][email protected]](http://upload-images.jianshu.io/upload_images/4266338-c88133055aa16992.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
就重写这点代码就可以实现这些效果了,是不是很简单?