Android 实现TwitterCoverScrollView
- 转载请注明来源和作者,谢谢!
先看实现的效果

为什么要去实现这个效果
在我写这篇文章前你可能看了相关实现该效果的方法,天下文章一大抄,各种实现的源码,讲解,你会发现其实都是出自某一个原创作者,当然,我不喜欢全盘照抄,没有自己的体会或者实现过程,一般也不写相关文章,我相信有人看过 TwitterCover-Android,这个效果是继承listview的基础上实现的,但是当我要写【设置】页面或者【我的】页面时,其实是不大适合使用listview去实现的。
- 下面讲解TwitterCover-Android(listview)要是实现【我的】界面方法。
1、TwitterCover-Android 自定义listview中addHeaderView一个layout,你要改顶部视图必须重新写这个布局。
2、顶部视图底下得布局必须重新写一个布局,然后使用addHeaderView装载这个视图。
3、在activity或者fragment中初始化控件时,必须分两步去实现,并且必定有适配器代码如下,可以感受下。
TwitterCoverListView listView = (TwitterCoverListView) fview.findViewById(R.id.layout_listview);
listView.setHeaderImage(BitmapFactory.decodeResource(getResources(), R.mipmap.per_bg));
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_expandable_list_item_1,
new String[]{}
);
View headView1 = listView.getHeaderView();
CircleImageView avatar = (CircleImageView) headView1.findViewById(R.id.avatar);
TextView nickname = (TextView) headView1.findViewById(R.id.nickname);
TextView authentication = (TextView) headView1.findViewById(R.id.authentication);
TextView publicInfo = (TextView) headView1.findViewById(R.id.publicInfo);
View headView2 = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_tab3_headview,null);
listView.addHeaderView(headView2);
listView.setAdapter(adapter);
这也就导致我放弃使用这个开源控件的原因,并且想办法去实现基于srcollview的效果。
TwitterCoverScrollView实现了那些功能。
- 下拉顶部图片放大效果,释放弹回。
- 布局完全不更改原有scrollview的写法,正常写即可。
- 可监听下拉动作,实现下拉刷新操作。
TwitterCover-Android 源码分析。
在写TwitterCoverScrollView前,我对TwitterCover-Android 进行了源码分析,大致如下。

1、继承listview。
2、init读取配置数据 并通过addheadview添加顶部视图。
3、overScrollby 方法监听下拉,通过requestLayout 实现视图刷新达到视图放大缩小效果。
4、stackBlur 实现模糊效果。
5、onTouchEvent 监听MotionEvent.ACTION_UP 动作实现释放回弹效果。
TwitterCoverScrollView 使用方法。
1、将原来scrollview替换成相应的TwitterCoverScrollView
2、在TwitterCoverScrollView 下按照正常习惯写布局,但要求钱三层必须是如下结构

- TwitterCoverScrollView ——> LinearLayout——>FrameLayout
布局示例源码如下:
<com.example.xsl.twittercoverscrollview_android.TwitterCoverScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@mipmap/fuwu_banner" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher_round" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="名字"
android:textColor="#666666" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
</com.example.xsl.twittercoverscrollview_android.TwitterCoverScrollView>
3、其他无任何要求,在FrameLayout内写顶部效果布局。其中imageview设置 android:scaleType="centerCrop"实现动态裁剪视图。
4、刷新监听方法,一次下拉只会刷新一次。
TwitterCoverScrollView twitterCoverScrollView = (TwitterCoverScrollView) findViewById(R.id.scrollView);
twitterCoverScrollView.setOnRefreshListener(new TwitterCoverScrollView.OnRefreshListener() {
@Override
public void onRefresh() {
Log.e("setOnRefreshListener","刷新了");
}
});
以上大致就是的TwitterCoverScrollView 使用方法介绍。
TwitterCoverScrollView 源码分析。
TwitterCoverScrollView 实现原理和TwitterCover-Android 有比较大的区别,视图不再试通过addview来添加,而是遍历已有视图精准定位,这也是为什么对视图布局的前三层有要求的原因。

1、onlayout实现遍历子view找到FrameLayout,读取原始高度。
2、islayout 重写返回false。为什么要返回false可以看requestLayout 方法实现和介绍。
3、ontuchevent监听MotionEvent.ACTION_UP 动作实现释放回弹效果。
4、OnRefreshListener 接口实现下拉监听。
- 建议大家还是看源码吧,实现比较简单,一看就懂。
源码如下:
/**
* Created by xsl on 2017/8/28.
* 自定义srcollview实现 TwitterCover 效果
* 增加下拉动作监听。
*/
public class TwitterCoverScrollView extends ScrollView {
private Context mContext;
/**
* 需要显示动画的控件容器
*/
private FrameLayout mFrameLayout;
/**
* 显示的背景图片
*/
private ImageView mImageView;
/**
* 能向下拉的最高倍数
*/
private double highMultiple = 1.6;
/**
* 是否已记录原始高度
*/
private boolean isRecord = false;
/**
* FrameLayout 原始高度
*/
private int fHeight;
/**
* FrameLayout实时高度
*/
private int cHeight;
/**
* 是否正在刷新
*/
private boolean isOnRefesh = false;
public TwitterCoverScrollView(Context context) {
this(context,null);
this.mContext = context;
}
public TwitterCoverScrollView(Context context, AttributeSet attrs) {
this(context, attrs,0);
this.mContext = context;
}
public TwitterCoverScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
setOverScrollMode(OVER_SCROLL_NEVER);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
int childCount = getChildCount();
for (int i=0;i<childCount;i++){
View childView = getChildAt(i);
if (childView instanceof LinearLayout){
int count = ((LinearLayout) childView).getChildCount();
for (int k=0;k<count;k++){
View view = ((LinearLayout) childView).getChildAt(k);
if (view instanceof FrameLayout) {
mFrameLayout = (FrameLayout) view;
// int count1 = ((FrameLayout) view).getChildCount();
// for (int j=0;j<count1;j++) {
// View view1 = ((FrameLayout) view).getChildAt(k);
// if (view1 instanceof FrameLayout) {
// mImageView = (ImageView) view1;
// }
// }
}
}
}
}
if (!isRecord) {
isRecord = true;
fHeight = mFrameLayout.getMeasuredHeight();
}
cHeight = mFrameLayout.getMeasuredHeight();
}
/**
* requestLayout
* @return
*/
@Override
public boolean isInLayout() {
return false;
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
//从顶部超过边界向下拉
if (deltaY<0 && cHeight < fHeight*highMultiple && isTouchEvent){
mFrameLayout.getLayoutParams().height = cHeight - deltaY/2;
mFrameLayout.requestLayout();
//下拉等于50dp这个点才会触发刷新动作,避免重复刷新
if (cHeight-fHeight>= dip2px(mContext,50) && !isOnRefesh) {
isOnRefesh = true;
onRefreshListener.onRefresh();
}
}
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 触摸手指抬起监听实现回弹动画
* @param ev
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_UP://抬起
if (fHeight < mFrameLayout.getHeight()) {
isOnRefesh = false;
ReleaseAnimimation animation = new ReleaseAnimimation(mFrameLayout, fHeight);
animation.setDuration(300);
mFrameLayout.startAnimation(animation);
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 回弹动画
*/
public class ReleaseAnimimation extends Animation {
final View mView;
final int mTargetHeight;
final int mExtraHeight;
protected ReleaseAnimimation(View view, int targetHeight) {
mView = view;
mTargetHeight = targetHeight;
mExtraHeight = mTargetHeight - view.getHeight();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mView.getLayoutParams().height = (int) (mTargetHeight - mExtraHeight * (1 - interpolatedTime));
mView.requestLayout();
}
}
/**
* 下拉监听接口
*/
public interface OnRefreshListener{
void onRefresh();
}
/**
* 设置下拉监听
*/
OnRefreshListener onRefreshListener;
public void setOnRefreshListener(OnRefreshListener onRefreshListener){
this.onRefreshListener = onRefreshListener;
}
}