效果图:
实现原理
利用属性动画的特性,动态地改变控件宽度。
这里唯一要注意的坑点在于我们一定要拿到准确的子控件(即内部几个view)的宽度与高度 才能精准地对要伸展以及收缩的宽度进行控制。同时,为了不让子控件内部由于文字挤压所带来的换行影响,这里可以设置textview的maxLine属性为1。
由于现在需求急于上线,这里先直接上代码了~等忙完这阵子再来细细分享实现过程中遇到的坑以及一些代码逻辑的实现原理。
Coding Time
class ExpandAnimationView : LinearLayout {
private lateinit var mImageView: ImageView
private val mAnimatorSet = AnimatorSet()
private var mExpandWidth = 0
private val mExpandContainer = LinearLayout(context)
private val mExpandItems = listOf(ExpandItem(R.drawable.ic_next, "收起"), ExpandItem(R.drawable.ic_share, "分享"))
private var mIsExpanding = false
private var mClickListener: OnExpandViewClickListener? = null
constructor(context: Context): this(context, null)
constructor(context: Context, attrs: AttributeSet?): super (context, attrs)
init {
// 设置父级控件的属性
orientation = HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
background = ResourcesCompat.getDrawable(context.resources, R.drawable.bg_expand_view, null)
mImageView = ImageView(context)
addView(mImageView)
mImageView.apply {
layoutParams = LinearLayout.LayoutParams(dip2px(25F), dip2px(25F))
val p = layoutParams as LayoutParams
p.setMargins(dip2px(13F), dip2px(13F), dip2px(13F), dip2px(13F))
layoutParams = p
setImageResource(R.drawable.ic_add)
}
setOnClickListener {
mImageView.performClick()
}
mImageView.setOnClickListener {
startAnimation()
}
// 设置扩展的子控件的属性
initExpandView()
}
private fun initExpandView() {
if (mExpandItems.isEmpty()) return
addView(mExpandContainer, 0)
mExpandContainer.layoutParams = LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT)
val containerParam = mExpandContainer.layoutParams as LayoutParams
mExpandContainer.gravity = Gravity.CENTER_VERTICAL
for ((index, item) in mExpandItems.withIndex()) {
val textView = TextView(context)
textView.apply {
gravity = Gravity.CENTER_VERTICAL
textView.textSize = 13F
textView.setTextColor(Color.WHITE)
textView.text = item.text
textView.maxLines = 1
val drawable = ResourcesCompat.getDrawable(context.resources, item.icon, null)
drawable?.setBounds(0, 0, dip2px(15F), dip2px(15F))
setCompoundDrawables(drawable, null, null, null)
compoundDrawablePadding = dip2px(3F)
}
mExpandContainer.addView(textView)
textView.setOnClickListener {
mClickListener?.onItemClick(item.text)
}
val p = textView.layoutParams as LinearLayout.LayoutParams
p.setMargins(if (index == 0) dip2px(23F) else dip2px(13F), 0, dip2px(13F), 0)
textView.layoutParams = p
val divider = View(context)
divider.setBackgroundColor(Color.parseColor("#fafafa"))
divider.layoutParams = LinearLayout.LayoutParams(dip2px(1F), dip2px(20F))
mExpandContainer.addView(divider)
mExpandWidth += (textView.textSize * item.text.length).toInt() + dip2px(15F + 20F + 1F + 3F)
}
mExpandWidth += dip2px(10F + 13F)
mExpandContainer.alpha = 0F
}
// 属性动画原理 与下面实现动画时 属性"expandWidth"相对应
fun setExpandWidth(width: Float) {
val p = mExpandContainer.layoutParams
p.width = width.toInt()
mExpandContainer.layoutParams = p
}
// 开始动画
fun startAnimation() {
if (mIsExpanding) {
collapse()
} else {
expand()
}
}
private fun expand() {
if (mIsExpanding) return
mIsExpanding = true
val rotateAnimation = ObjectAnimator.ofFloat(mImageView, "rotation", 0F, -45F)
val alphaAnimation = ObjectAnimator.ofFloat(mImageView, "alpha", 1F, 0.5F)
val expandAnim = ObjectAnimator.ofFloat(this, "expandWidth", 0F, mExpandWidth.toFloat())
val alphaAnim = ObjectAnimator.ofFloat(mExpandContainer, "alpha", 0F, 1F)
mAnimatorSet.playTogether(rotateAnimation, alphaAnimation, expandAnim, alphaAnim)
mAnimatorSet.start()
mClickListener?.onExpand()
}
private fun collapse() {
if (!mIsExpanding) return
mIsExpanding = false
val rotateAnimation = ObjectAnimator.ofFloat(mImageView, "rotation", -45F, 0F)
val alphaAnimation = ObjectAnimator.ofFloat(mImageView, "alpha", 0.5F, 1F)
val collapseAnim = ObjectAnimator.ofFloat(this, "expandWidth", mExpandWidth.toFloat(), 0F)
val alphaAnim = ObjectAnimator.ofFloat(mExpandContainer, "alpha", 1F, 0F)
mAnimatorSet.playTogether(rotateAnimation, alphaAnimation, collapseAnim, alphaAnim)
mAnimatorSet.start()
mClickListener?.onCollapse()
}
fun setOnExpandViewClickListener(lis: OnExpandViewClickListener) {
this.mClickListener = lis
}
fun destroy() {
mAnimatorSet.removeAllListeners()
mAnimatorSet.cancel()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(measuredWidth, dip2px(25F + 26F))
}
private fun dip2px(floatVal: Float): Int {
val density = context.resources.displayMetrics.density
return (floatVal * density + 0.5F).toInt()
}
data class ExpandItem(@DrawableRes val icon: Int, val text: String)
interface OnExpandViewClickListener {
fun onExpand()
fun onCollapse()
fun onItemClick(tag: String)
}
}
若需要Java代码的同学可以自行在AS里通过Tools -> Kotlin -> Show Kotlin Bytecode -> 在右侧弹出的窗口中选择Decompile即可。
欢迎指出不足~
Java代码:
public class ExpandAnimationView extends LinearLayout {
private ImageView mImageView;
private final AnimatorSet mAnimatorSet = new AnimatorSet();
private int mExpandWidth = 0;
private final LinearLayout mExpandContainer = new LinearLayout(getContext());
private final List<ExpandItem> mExpandItems = new ArrayList<>();
private boolean mIsExpanding = false;
private OnExpandViewClickListener mClickListener;
public ExpandAnimationView(Context context) {
this(context, null);
}
public ExpandAnimationView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
mExpandItems.add(new ExpandItem(R.drawable.ic_next, "收起"), new ExpandItem(R.drawable.ic_share, "分享"));
initViews();
}
private void initViews() {
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
setBackground(ResourcesCompat.getDrawable(getContext().getResources(), R.drawable.bg_expand_view, null));
mImageView = new ImageView(getContext());
addView(mImageView);
mImageView.setLayoutParams(new LinearLayout.LayoutParams(dip2px(25F), dip2px(25F)));
LinearLayout.LayoutParams p = (LayoutParams) mImageView.getLayoutParams();
p.setMargins(dip2px(13F), dip2px(13F), dip2px(13F), dip2px(13F));
mImageView.setLayoutParams(p);
mImageView.setImageResource(R.drawable.ic_add);
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mImageView.performClick();
}
});
mImageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
startAnimation();
}
});
initExpandView();
}
private void initExpandView() {
if (mExpandItems == null || mExpandItems.isEmpty()) return;
addView(mExpandContainer, 0);
mExpandContainer.setLayoutParams(new LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT));
ViewGroup.LayoutParams lp = mExpandContainer.getLayoutParams();
mExpandContainer.setGravity(Gravity.CENTER_VERTICAL);
for (int i = 0; i < mExpandItems.size(); i++) {
final TextView textView = new TextView(getContext());
textView.setGravity(Gravity.CENTER_VERTICAL);
textView.setTextSize(13F);
textView.setTextColor(Color.WHITE);
textView.setText(mExpandItems.get(i).text);
textView.setMaxLines(1);
Drawable drawable = ResourcesCompat.getDrawable(getContext().getResources(), mExpandItems.get(i).resourceId, null);
if (drawable != null) {
drawable.setBounds(0, 0, dip2px(15F), dip2px(15F));
}
textView.setCompoundDrawables(drawable, null, null, null);
textView.setCompoundDrawablePadding(dip2px(3F));
mExpandContainer.addView(textView);
textView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (mClickListener != null) {
mClickListener.onItemClick(mExpandItems.get(i).text);
}
}
});
LinearLayout.LayoutParams p = (LayoutParams) textView.getLayoutParams();
p.setMargins(i == 0 ? dip2px(23F) : dip2px(13F), 0, dip2px(13F), 0);
textView.setLayoutParams(p);
final View divider = new View(getContext());
divider.setBackgroundColor(Color.parseColor("#fafafa"));
divider.setLayoutParams(new LinearLayout.LayoutParams(dip2px(1F), dip2px(20F)));
mExpandContainer.addView(divider);
mExpandWidth += (textView.getTextSize() * mExpandItems.get(i).text.length()) + dip2px(15F + 20F + 1F + 3F);
}
mExpandWidth += dip2px(10F + 13F);
mExpandContainer.setAlpha(0);
}
// 属性动画原理 与下面实现动画时 属性"expandWidth"相对应
public void setExpandWidth(float width) {
LayoutParams p = (LayoutParams) mExpandContainer.getLayoutParams();
p.width = (int) width;
mExpandContainer.setLayoutParams(p);
}
// 开始动画
public void startAnimation() {
if (mIsExpanding) {
collapse();
} else {
expand();
}
}
private void collapse() {
if (!mIsExpanding) return;
mIsExpanding = false;
final ObjectAnimator rotateAnimation = ObjectAnimator.ofFloat(mImageView, "rotation", -45F, 0F);
final ObjectAnimator alphaAnimation = ObjectAnimator.ofFloat(mImageView, "alpha", 0.5F, 1F);
final ObjectAnimator collapseAnim = ObjectAnimator.ofFloat(this, "expandWidth", mExpandWidth, 0F);
final ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mExpandContainer, "alpha", 1F, 0F);
mAnimatorSet.playTogether(rotateAnimation, alphaAnimation, collapseAnim, alphaAnim);
mAnimatorSet.start();
if (mClickListener != null) {
mClickListener.onCollapse();
}
}
private void expand() {
if (mIsExpanding) return;
mIsExpanding = true;
final ObjectAnimator rotateAnimation = ObjectAnimator.ofFloat(mImageView, "rotation", 0F, -45F);
final ObjectAnimator alphaAnimation = ObjectAnimator.ofFloat(mImageView, "alpha", 1F, 0.5F);
final ObjectAnimator expandAnim = ObjectAnimator.ofFloat(this, "expandWidth", 0F, mExpandWidth);
final ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mExpandContainer, "alpha", 0F, 1F);
mAnimatorSet.playTogether(rotateAnimation, alphaAnimation, expandAnim, alphaAnim);
mAnimatorSet.start();
if (mClickListener != null) {
mClickListener.onExpand();
}
}
private int dip2px(float floatVal) {
float density = getContext().getResources().getDisplayMetrics().density;
return (int) (floatVal * density + 0.5F);
}
public void setOnExpandViewClickListener(OnExpandViewClickListener clickListener) {
this.mClickListener = clickListener;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), dip2px(25F + 26F));
}
public void destroy() {
mAnimatorSet.removeAllListeners();
mAnimatorSet.cancel();
}
class ExpandItem {
public int resourceId;
public String text;
}
interface OnExpandViewClickListener {
void onExpand();
void onCollapse();
void onItemClick(String tag);
}
}