遇到一个用iPhone的产品是一种怎样的体验?那就是不管做什么功能,遇到什么需求,安卓的效果必须要和ios一致,难受...
好吧,扯远了!今天要给大家展示的是一个IOS风格的标题栏搜索框,效果图如下:
我们先说一下实现原理。滚动列表时,通过ObjectAnimator改变搜索框的宽度来控制搜索框的展开和收回;点击搜索框打开搜索详情页面,用的安卓5.0加入共享元素转场动画。如果对转场动画不了解的同学,可以点击链接了解一下。下面我来带大家看看关键部分代码。
由于我们需要用到属性动画,而我们用到的ObjectAnimator 执行动画时需要对应属性有get set方法,因此通过 ViewWrapper 提供get set 方法封装view。
public class ViewWrapper {
private View mTarget;
public ViewWrapper(View target) {
mTarget = target;
}
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
public int getHeight() {
return mTarget.getLayoutParams().height;
}
public void setHeight(int height) {
mTarget.getLayoutParams().height = height;
mTarget.requestLayout();
}
}
然后我们自定义一个搜索框SearchTitleBar
public class SearchTitleBar extends LinearLayout implements OnLimitClickListener {
private final int ANIM_TYPE_START = 1;
private final int ANIM_TYPE_RESET = 2;
@BindView(R.id.holder_view)
View holderView;
@BindView(R.id.search_layout)
LinearLayout searchLayout;
@BindView(R.id.search_tv)
TextView searchTv;
@BindView(R.id.search_iv)
ImageView searchIv;
@BindView(R.id.search_edit)
TextView searchEt;
@BindView(R.id.divide_view)
View divideView;
private OnSearchTitleBarListener onSearchTitleBarListener = null;
private ViewWrapper wrapper = null;
private ObjectAnimator animator = null;
private int curAnimType = 0;
private int holderViewWidth = 0;
private int searchLayoutInitWidth = 0;
public void setOnSearchTitleBarListener(OnSearchTitleBarListener onSearchTitleBarListener) {
this.onSearchTitleBarListener = onSearchTitleBarListener;
}
public SearchTitleBar(Context context) {
super(context);
builder(context);
}
public SearchTitleBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
builder(context);
}
public SearchTitleBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
builder(context);
}
private void builder(Context context) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.search_title_bar_layout, null);
view.setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 1.0f));
searchLayoutInitWidth = context.getResources().getDimensionPixelSize(R.dimen.search_init_width);
ButterKnife.bind(this, view);
divideView.setVisibility(View.GONE);
wrapper = new ViewWrapper(searchLayout);
this.addView(view);
searchLayout.setOnClickListener(new OnLimitClickHelper(this));
}
public View getSearchLayout() {
return searchLayout;
}
public View getSearchTv() {
return searchTv;
}
public View getSearchEt() {
return searchEt;
}
public View getSearchIv() {
return searchIv;
}
public void setHint(String hint) {
if (TextUtils.isEmpty(hint)) {
searchEt.setPadding(0, 0, 0, 0);
} else {
searchEt.setPadding(Utils.getResourcesDimension(R.dimen.dimen_size_5), 0, 0, 0);
}
searchEt.setText(hint);
}
public void showDivide() {
divideView.setVisibility(View.VISIBLE);
}
public void hideDivide() {
divideView.setVisibility(View.GONE);
}
public void performAnimate(final boolean startOrReset) {
if (holderViewWidth == 0) {
holderViewWidth = holderView.getWidth();
}
int animType = 0;
int endWidth = 0;
if (startOrReset) {
animType = ANIM_TYPE_START;
endWidth = holderViewWidth;
searchLayout.setBackgroundResource(R.drawable.shape_search_gray);
} else {
animType = ANIM_TYPE_RESET;
endWidth = searchLayoutInitWidth;
searchLayout.setBackgroundResource(R.drawable.shape_search_white);
}
if (curAnimType != animType) {
if (animator != null) {
animator.cancel();
curAnimType = animType;
}
} else {
return;
}
animator = ObjectAnimator.ofInt(wrapper, "width", endWidth);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
hideDivide();
if (startOrReset) {
setHint("输入搜索关键字");
} else {
setHint("搜索");
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (curAnimType == ANIM_TYPE_START) {
showDivide();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.setDuration(300);
animator.start();
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.search_layout) {
if (onSearchTitleBarListener != null) {
onSearchTitleBarListener.onClickSearch();
}
}
}
public interface OnSearchTitleBarListener {
void onClickSearch();
}
}
布局文件
这里我们定义了一个holderView,目的是通过holderView来测量搜索框展开时的宽度。
然后我们在activity中监听RecylerView的滚动来控制搜索框的展开和收回
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
setupTitleBar();
}
});
public void setupTitleBar() {
if (recyclerView == null || titleBar == null) {
return;
}
int top = getRvScollY();
if(searchBarState == 0 && top >= tagetHeight){
searchBarState = 1;
titleBar.performAnimate(true);
} else if((top >= tagetHeight) && (top <= resetTagetHeight)){
titleBar.hideDivide();
} else if(searchBarState == 1 && top <= resetTagetHeight){
searchBarState = 0;
titleBar.performAnimate(false);
}
if (Math.abs(top) <= tagetHeight) {
float precent = (Math.abs(top) * 1.0f / tagetHeight);
int alpha = (int) (precent * 255);
titleBar.setBackgroundColor(Color.argb(alpha, 255, 255, 255));
} else {
titleBar.setBackgroundColor(Color.argb(255, 255, 255, 255));
}
}
public int getRvScollY() {
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int position = layoutManager.findFirstVisibleItemPosition();
View firstVisiableChildView = layoutManager.findViewByPosition(position);
int itemHeight = firstVisiableChildView.getHeight();
return (position) * itemHeight - firstVisiableChildView.getTop();
}
这个地方的关键点就是我们需要获取rv滚动的距离,由于rv item是复用的,所以我们没法通过第一个可见item getTop的方法来获取滚动距离。因此我们采用的方式是获取第一个可见item的位置乘以item的高度再减去首个可见item距离顶部的距离即可获取到rv真实的滚动距离。
最后转场动画
Intent intent = new Intent(this, SearchDetailActivity.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this,
Pair.create(titleBar.getSearchLayout()
, Utils.getResourcesString(R.string.trans_anim_search_layout)),
Pair.create(titleBar.getSearchTv()
, Utils.getResourcesString(R.string.trans_anim_search_tv)),
Pair.create(titleBar.getSearchIv()
, Utils.getResourcesString(R.string.trans_anim_search_iv)),
Pair.create(titleBar.getSearchEt()
, Utils.getResourcesString(R.string.trans_anim_search_edit))).toBundle());
} else {
startActivity(intent);
}
值得注意的是这里需要做一下版本判断,因为5.0以下的版本是不支持共享元素的。
源码请戳!