项目地址:https://github.com/BitToNet/FeloneHelper/blob/master/README.md#可用于商品展示的锚点定位加联动布局
演示
这个是网上找的一个项目,修复了一些bug
演示代码入口在CActivity里面,能看懂代码就不用看下面了
dependencies {
api "com.android.support:design:${SUPPORT_VERSION}"
}
1.拷贝自定义widget/CustomScrollView
2.添加布局,在id为ll_top的LinearLayout里加头部
3.拷贝CActivity中四个方法,并在init中处理自己的数据
4.仿写一个AnchorView,在布局R.layout.view_anchor自定义自己的子模块界面
如果子模块中加了recycleview,一定要加这么一条代码
recyclerView.setNestedScrollingEnabled(false);
新建CustomScrollView继承NestedScrollView,通过回调讲两个方法暴露出去
onScrollChanged(滚动监听)
onInterceptTouchEvent(事件分发,监听它的原因是有时候CustomScrollView里面的子view会消费事件,导致CustomScrollView接受不到事件,所以在分发的时候传给CustomScrollView)
代码如下:
public class CustomScrollView extends NestedScrollView {
public Callbacks mCallbacks;
public CustomScrollView(Context context) {
super(context);
}
public CustomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setCallbacks(Callbacks callbacks) {
this.mCallbacks = callbacks;
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mCallbacks != null) {
mCallbacks.onScrollChanged(l, t, oldl, oldt);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mCallbacks != null) {
mCallbacks.ontouch(ev);
}
return super.onInterceptTouchEvent(ev);
}
//定义接口用于回调
public interface Callbacks {
void onScrollChanged(int x, int y, int oldx, int oldy);
void ontouch(MotionEvent ev);
}
}
这个布局包含四部分:
头部LinearLayout
占位TabLayout
实际操作的TabLayout
底部内容的LinearLayout
这里最重要的就是两个tablayout,可以看一下他们在ScrollView滑动时的运动情况
在顶部时,两个tablayout叠加在一起
演示
滑到下面时,一个留着原地,一个跟着ScrollView移动位置,使自己保持在顶部演示
具体布局代码如下:
根据滑动的距离改变tablayout的位置,这样就行实现了悬停的效果
scrollView.setCallbacks(new CustomScrollView.Callbacks() {
@Override
public void onScrollChanged(int x, int y, int oldx, int oldy) {
//根据滑动的距离y(不断变化的) 和 holderTabLayout距离父布局顶部的距离(这个距离是固定的)对比,
//当y < holderTabLayout.getTop()时,holderTabLayout 仍在屏幕内,realTabLayout不断移动holderTabLayout.getTop()距离,覆盖holderTabLayout
//当y > holderTabLayout.getTop()时,holderTabLayout 移出,realTabLayout不断移动y,相对的停留在顶部,看上去是静止的
int translation = Math.max(y, holderTabLayout.getTop());
realTabLayout.setTranslationY(translation);
if (isScroll) {
for (int i = tabTxt.length - 1; i >= 0; i--) {
//需要y减去顶部内容区域的高度
if (y - mLlTop.getMeasuredHeight() > anchorList.get(i).getTop() - 10) {
setScrollPos(i);
break;
}
}
}
}
完整代码如下:
public class CActivity extends AppCompatActivity {
/**
* 占位tablayout,用于滑动过程中去确定实际的tablayout的位置
*/
private TabLayout holderTabLayout;
/**
* 实际操作的tablayout,
*/
private TabLayout realTabLayout;
private CustomScrollView scrollView;
private LinearLayout container;
private LinearLayout mLlTop;
private String[] tabTxt = {"客厅", "卧室", "餐厅", "书房", "阳台", "儿童房"};
private List anchorList = new ArrayList<>();
//判读是否是scrollview主动引起的滑动,true-是,false-否,由tablayout引起的
private boolean isScroll;
//记录上一次位置,防止在同一内容块里滑动 重复定位到tablayout
private int lastPos = 0;
//监听判断最后一个模块的高度,不满一屏时让最后一个模块撑满屏幕
private ViewTreeObserver.OnGlobalLayoutListener listener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_c);
holderTabLayout = findViewById(R.id.tablayout_holder);
realTabLayout = findViewById(R.id.tablayout_real);
scrollView = findViewById(R.id.scrollView);
container = findViewById(R.id.container);
mLlTop = findViewById(R.id.ll_top);
init();
}
private void init() {
for (int i = 0; i < tabTxt.length; i++) {
AnchorView anchorView = new AnchorView(this);
anchorView.setAnchorTxt(tabTxt[i]);
anchorView.setContentTxt(tabTxt[i]);
anchorList.add(anchorView);
container.addView(anchorView);
}
for (int i = 0; i < tabTxt.length; i++) {
holderTabLayout.addTab(holderTabLayout.newTab().setText(tabTxt[i]));
realTabLayout.addTab(realTabLayout.newTab().setText(tabTxt[i]));
}
listener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//计算让最后一个view高度撑满屏幕
int screenH = getScreenHeight();
int statusBarH = getStatusBarHeight(CActivity.this);
int tabH = holderTabLayout.getHeight();
int lastH = screenH - statusBarH - tabH - 16 * 3;
AnchorView anchorView = anchorList.get(anchorList.size() - 1);
if (anchorView.getHeight() < lastH) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
params.height = lastH;
anchorView.setLayoutParams(params);
}
//一开始让实际的tablayout 移动到 占位的tablayout处,覆盖占位的tablayout
realTabLayout.setTranslationY(holderTabLayout.getTop());
realTabLayout.setVisibility(View.VISIBLE);
container.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
};
container.getViewTreeObserver().addOnGlobalLayoutListener(listener);
//监听scrollview滑动
scrollView.setCallbacks(new CustomScrollView.Callbacks() {
@Override
public void onScrollChanged(int x, int y, int oldx, int oldy) {
//根据滑动的距离y(不断变化的) 和 holderTabLayout距离父布局顶部的距离(这个距离是固定的)对比,
//当y < holderTabLayout.getTop()时,holderTabLayout 仍在屏幕内,realTabLayout不断移动holderTabLayout.getTop()距离,覆盖holderTabLayout
//当y > holderTabLayout.getTop()时,holderTabLayout 移出,realTabLayout不断移动y,相对的停留在顶部,看上去是静止的
int translation = Math.max(y, holderTabLayout.getTop());
realTabLayout.setTranslationY(translation);
if (isScroll) {
for (int i = tabTxt.length - 1; i >= 0; i--) {
//需要y减去顶部内容区域的高度
if (y - mLlTop.getMeasuredHeight() > anchorList.get(i).getTop() - 10) {
setScrollPos(i);
break;
}
}
}
}
@Override
public void ontouch(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
isScroll = true;
}
}
});
//实际的tablayout的点击切换
realTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
isScroll = false;
int pos = tab.getPosition();
Log.d("=======pos", "" + pos);
int top = anchorList.get(pos).getTop();
//同样这里滑动要加上顶部内容区域的高度(这里写死的高度)
scrollView.smoothScrollTo(0, top + mLlTop.getMeasuredHeight());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
isScroll = false;
int pos = tab.getPosition();
int top = anchorList.get(pos).getTop();
//同样这里滑动要加上顶部内容区域的高度
scrollView.smoothScrollTo(0, top +mLlTop.getMeasuredHeight());
}
});
}
private void setScrollPos(int newPos) {
if (lastPos != newPos) {
realTabLayout.setScrollPosition(newPos, 0, true);
}
lastPos = newPos;
}
private int getScreenHeight() {
return getResources().getDisplayMetrics().heightPixels;
}
public int getStatusBarHeight(Context context) {
int result = 0;
int resourceId = context.getResources()
.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
}
项目地址:https://github.com/BitToNet/FeloneHelper/blob/master/README.md#可用于商品展示的锚点定位加联动布局