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
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ExpandableLinearLayout">
<!--默认显示的条目数-->
<attr name="defaultItemCount" format="integer" />
<!--提示文字的大小-->
<attr name="tipTextSize" format="dimension" />
<!--字体颜色-->
<attr name="tipTextColor" format="color"/>
<!--待展开的文字提示-->
<attr name="expandText" format="string" />
<!--待收起时的文字提示-->
<attr name="hideText" format="string" />
<!--向下的箭头的图标-->
<attr name="arrowDownImg" format="reference" />
<!--是否使用默认的底部-->
<attr name="useDefaultBottom" format="boolean" />
</declare-styleable>
</resources>
item_ell_bottom.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="10dp"
>
<TextView
android:id="@+id/tv_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="查看更多"
/>
<ImageView
android:id="@+id/iv_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="5dp"
android:layout_toRightOf="@id/tv_tip"
/>
</RelativeLayout>
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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp"
>
<ImageView
android:id="@+id/iv_img"
android:layout_width="100dp"
android:layout_height="80dp"
android:layout_gravity="center"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical"
>
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="隆江猪脚饭"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center"
android:orientation="horizontal"
>
<TextView
android:id="@+id/tv_intro"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="特别好吃哦,带回家啊时间还是健康的贺卡上空间的挥洒健康的贺卡姐啊上课黑色的健康哈空间"
android:textColor="#9f9f9f"
/>
<TextView
android:id="@+id/tv_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:drawablePadding="5dp"
android:text="12.00"
/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#D1D1D1"/>
</LinearLayout>
最后添加依赖:
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.jakewharton:butterknife:7.0.0'
添加网络权限:
<uses-permission android:name="android.permission.INTERNET"/>
效果图:
学习来源:http://blog.youkuaiyun.com/chay_chan/article/details/72810770