package com.loaderman.expandablelinearlayout;import android.animation.ObjectAnimator;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Color;import android.util.AttributeSet;import android.util.Log;import android.view.View;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.TextView;/** * 可以展开的LinearLayout */public class ExpandableLinearLayout extends LinearLayout implements View.OnClickListener { private static final String TAG = ExpandableLinearLayout.class.getSimpleName(); private TextView tvTip; private ImageView ivArrow; private boolean isExpand = false;//是否是展开状态,默认是隐藏 private int defaultItemCount;//一开始展示的条目数 private String expandText;//待展开显示的文字 private String hideText;//待隐藏显示的文字 private boolean useDefaultBottom;//是否使用默认的底部,默认为true使用默认的底部 private boolean hasBottom;//是否已经有底部,默认为false,没有 private View bottomView; private float fontSize; private int textColor; private int arrowResId; public ExpandableLinearLayout(Context context) { this(context, null); } public ExpandableLinearLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ExpandableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ExpandableLinearLayout); defaultItemCount = ta.getInt(R.styleable.ExpandableLinearLayout_defaultItemCount, 2); expandText = ta.getString(R.styleable.ExpandableLinearLayout_expandText); hideText = ta.getString(R.styleable.ExpandableLinearLayout_hideText); fontSize = ta.getDimension(R.styleable.ExpandableLinearLayout_tipTextSize, UIUtils.sp2px(context, 14)); textColor = ta.getColor(R.styleable.ExpandableLinearLayout_tipTextColor, Color.parseColor("#666666")); arrowResId = ta.getResourceId(R.styleable.ExpandableLinearLayout_arrowDownImg, R.mipmap.arrow_down); useDefaultBottom = ta.getBoolean(R.styleable.ExpandableLinearLayout_useDefaultBottom, true); ta.recycle(); setOrientation(VERTICAL); } /** * 渲染完成时初始化默认底部view */ @Override protected void onFinishInflate() { super.onFinishInflate(); findViews(); } /** * 初始化底部view */ private void findViews() { bottomView = View.inflate(getContext(), R.layout.item_ell_bottom, null); ivArrow = (ImageView) bottomView.findViewById(R.id.iv_arrow); tvTip = (TextView) bottomView.findViewById(R.id.tv_tip); tvTip.getPaint().setTextSize(fontSize); tvTip.setTextColor(textColor); ivArrow.setImageResource(arrowResId); bottomView.setOnClickListener(this); } public void addItem(View view) { int childCount = getChildCount(); if (!useDefaultBottom){ //如果不使用默认底部 addView(view); if (childCount > defaultItemCount){ hide(); } return; } //使用默认底部 if (!hasBottom) { //如果还没有底部 addView(view); } else { addView(view, childCount - 2);//插在底部之前 } refreshUI(view); } @Override public void setOrientation(int orientation) { if (LinearLayout.HORIZONTAL == orientation) { throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation."); } super.setOrientation(orientation); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int childCount = getChildCount(); Log.i(TAG, "childCount: " + childCount); justToAddBottom(childCount); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 判断是否要添加底部 * @param childCount */ private void justToAddBottom(int childCount) { if (childCount > defaultItemCount) { if (useDefaultBottom && !hasBottom) { //要使用默认底部,并且还没有底部 addView(bottomView);//添加底部 hide(); hasBottom = true; } } } /** * 刷新UI * * @param view */ private void refreshUI(View view) { int childCount = getChildCount(); if (childCount > defaultItemCount) { if (childCount - defaultItemCount == 1) { //刚超过默认,判断是否要添加底部 justToAddBottom(childCount); } view.setVisibility(GONE);//大于默认数目的先隐藏 } } /** * 展开 */ private void expand() { for (int i = defaultItemCount; i < getChildCount(); i++) { //从默认显示条目位置以下的都显示出来 View view = getChildAt(i); view.setVisibility(VISIBLE); } } /** * 收起 */ private void hide() { int endIndex = useDefaultBottom ? getChildCount() - 1 : getChildCount();//如果是使用默认底部,则结束的下标是到底部之前,否则则全部子条目都隐藏 for (int i = defaultItemCount; i < endIndex; i++) { //从默认显示条目位置以下的都隐藏 View view = getChildAt(i); view.setVisibility(GONE); } } // 箭头的动画 private void doArrowAnim() { if (isExpand) { // 当前是展开,将执行收起,箭头由上变为下 ObjectAnimator.ofFloat(ivArrow, "rotation", -180, 0).start(); } else { // 当前是收起,将执行展开,箭头由下变为上 ObjectAnimator.ofFloat(ivArrow, "rotation", 0, 180).start(); } } @Override public void onClick(View v) { toggle(); } public void toggle() { if (isExpand) { hide(); tvTip.setText(expandText); } else { expand(); tvTip.setText(hideText); } doArrowAnim(); isExpand = !isExpand; //回调 if (mListener != null){ mListener.onStateChanged(isExpand); } } private OnStateChangeListener mListener; /** * 定义状态改变接口 */ public interface OnStateChangeListener { void onStateChanged(boolean isExpanded); } public void setOnStateChangeListener(OnStateChangeListener mListener) { this.mListener = mListener; }}
UIUtil.java
package com.loaderman.expandablelinearlayout;import android.content.Context;public class UIUtils { /** * dip-->px */ public static int dip2Px(Context context,int dip) { // px/dip = density; // density = dpi/160 // 320*480 density = 1 1px = 1dp // 1280*720 density = 2 2px = 1dp float density = context.getResources().getDisplayMetrics().density; int px = (int) (dip * density + 0.5f); return px; } /** * 将sp值转换为px值,保证文字大小不变 * * @param spValue * @return */ public static int sp2px(Context context,float spValue) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (spValue * fontScale + 0.5f); }}
attr.xml
item_ell_bottom.xml
ProductBean.java
package com.loaderman.expandablelinearlayout;public class ProductBean { private String img; private String name; private String intro; private String price; public ProductBean(String img, String name, String intro, String price) { this.img = img; this.name = name; this.intro = intro; this.price = price; } public String getImg() { return img; } public void setImg(String img) { this.img = img; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getIntro() { return intro; } public void setIntro(String intro) { this.intro = intro; } public String getPrice() { return price; } public void setPrice(String price) { this.price = price; }}
MainActivity.java
package com.loaderman.expandablelinearlayout;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.ImageView;import android.widget.TextView;import com.bumptech.glide.Glide;import butterknife.Bind;import butterknife.ButterKnife;public class MainActivity extends AppCompatActivity { @Bind(R.id.ell_product) ExpandableLinearLayout ellProduct; private String[] imgUrls = new String[]{ "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1496728066&di=e5669ad80a241da52b03301ee0ba2749&imgtype=jpg&er=1&src=http%3A%2F%2Fimg.taopic.com%2Fuploads%2Fallimg%2F121017%2F240425-12101H2202646.jpg", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1496728145&di=c2ece04e1445eaf91fe3f3bf12ad1080&imgtype=jpg&er=1&src=http%3A%2F%2Fimg1.qunarzz.com%2Ftravel%2Fd6%2F1610%2F33%2F21ce9c91e70ab7b5.jpg_r_720x480x95_b2bcd2c5.jpg", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1496728182&di=1e06ea8b74863155b9d52736093beda8&imgtype=jpg&er=1&src=http%3A%2F%2Fe.hiphotos.baidu.com%2Fbainuo%2Fcrop%3D0%2C0%2C470%2C285%3Bw%3D470%3Bq%3D79%2Fsign%3Da8aa38e3b73533fae1f9c96e95e3d12f%2F6c224f4a20a44623b885148f9e22720e0df3d794.jpg", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1496133522433&di=1132cb36274a205f8ce30e21f47a37ee&imgtype=0&src=http%3A%2F%2Fi3.s2.dpfile.com%2Fpc%2Fb68a2a4316ae56373e83ce65ad7dfada%2528249x249%2529%2Fthumb.jpg", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1496728305&di=444bfe10c434c09043855e7a6a7f8ace&imgtype=jpg&er=1&src=http%3A%2F%2Fe.hiphotos.baidu.com%2Fbainuo%2Fcrop%3D0%2C0%2C470%2C285%3Bw%3D470%3Bq%3D99%2Fsign%3D65498f21374e251ff6b8beb89ab6e527%2F0df3d7ca7bcb0a46d662a6226c63f6246b60af6c.jpg" }; private String[] names = new String[]{ "炒河粉", "炒米粉", "隆江猪脚饭", "烧鸭饭", "叉烧饭" }; private String[] intros = new String[]{ "好吃又不腻", "精选上等米粉,绝对好吃", "隆江猪脚饭,肥而不腻,入口香爽,深受广东人民的喜爱", "简单而美味,充满烧腊香味", "色香味俱全" }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); ellProduct.removeAllViews();//清除所有的子View(避免重新刷新数据时重复添加) //添加数据 for (int i = 0; i < 5; i++) { View view = View.inflate(this, R.layout.item_product, null); ProductBean productBean = new ProductBean(imgUrls[i], names[i], intros[i], "12.00"); ViewHolder viewHolder = new ViewHolder(view, productBean); viewHolder.refreshUI(); ellProduct.addItem(view);//添加子条目 } }class ViewHolder { @Bind(R.id.iv_img) ImageView ivImg; @Bind(R.id.tv_name) TextView tvName; @Bind(R.id.tv_intro) TextView tvIntro; @Bind(R.id.tv_price) TextView tvPrice; ProductBean productBean; public ViewHolder(View view, ProductBean productBean) { ButterKnife.bind(this, view); this.productBean = productBean; } private void refreshUI() { Glide.with(MainActivity.this) .load(productBean.getImg()) .placeholder(R.mipmap.ic_launcher) .into(ivImg); tvName.setText(productBean.getName()); tvIntro.setText(productBean.getIntro()); tvPrice.setText("¥" + productBean.getPrice()); } }}
item_product.xml
最后添加依赖:
compile 'com.github.bumptech.glide:glide:3.7.0' compile 'com.jakewharton:butterknife:7.0.0'
添加网络权限:
效果图:
学习来源: