更多动态视图MoreNewsView
经常看朋友圈的动态,有的动态内容较多就只展示前面一段,如果用户想看完整的再点击展开,这样整个页面的动态列表比较均衡,不会出现个别动态占用大片屏幕的情况。同样,查看博客的文章列表也类似,只展示文章开头几行内容,有需要再点击加载全篇文章。
动态列表直接使用ListView,动态内容就得自己写个控件了,自定义控件的难点在于如何把握动态下拉和收起的动画。这里我们要先预习TextView的相关函数,下面是本文用到的方法说明:
getHeight : 获取TextView的显示高度。
setHeight : 设置TextView的显示高度。
getLineHeight : 获取每行文本的高度。
getLineCount : 获取所有文本的行数。
如果一开始每条动态默认显示四行,那么默认显示高度是getLineHeight*4,使用setHeight方法即可设置动态的初始显示高度。点击展开动态全文时,就得显示所有行的文本,整个文本的高度是getLineHeight*getLineCount。现在有了每条动态的初始高度,以及动态全文的完整高度,再加个拉伸动画就差不多了。拉伸动画的主要工作是随着时间的推移,给TextView设置渐增或渐减的高度,这要重写Animation的applyTransformation方法。
下面是点击监听器的显示动画代码示例:
private OnClickListener mOnClickListener = new View.OnClickListener() {
boolean isExpand;
@Override
public void onClick(View v) {
tv_expand.setText(isExpand?"查看全文":"收起关注");
isExpand = !isExpand;
tv_content.clearAnimation();
final int deltaValue;
final int startValue = tv_content.getHeight();
int durationMillis = 300;
if (isExpand) {
deltaValue = tv_content.getLineHeight() * tv_content.getLineCount() - startValue;
} else {
deltaValue = tv_content.getLineHeight() * maxLine - startValue;
}
Animation animation = new Animation() {
protected void applyTransformation(float interpolatedTime, Transformation t) {
tv_content.setHeight((int) (startValue + deltaValue * interpolatedTime));
}
};
animation.setDuration(durationMillis);
tv_content.startAnimation(animation);
}
};
下面是展开/收起朋友圈动态详情的效果截图
可折叠列表ExpandableListView
嵌套列表ExpandableListView是又一种常见的控件,常见的业务场景包括:好友分组与好友列表、订单列表与订单内的商品列表、邮件夹分组与邮件列表等等。
ExpandableListView常用方法
Android自带的ExpandableListView可以直接用于嵌套列表,点击一个组,展开该组下的子列表;再点击这个组,收起该组下的子列表。
下面是ExpandableListView的常用方法说明:
setAdapter : 设置适配器。适配器类型为ExpandableListAdapter
expandGroup : 展开指定分组。
collapseGroup : 收起指定分组。
isGroupExpanded : 判断指定分组是否展开。
setSelectedGroup : 设置选中的分组。
setSelectedChild : 设置选中的子项。
setGroupIndicator : 设置指定分组的指示图像。
setChildIndicator : 设置指定子项的指示图像。
ExpandableListView监听器
除了OnItemClickListener,ExpandableListView新加了下面几个监听器:
1、分组展开事件,相关类名与方法说明如下:
监听器类名 : OnGroupExpandListener
设置监听器的方法 : setOnGroupExpandListener
监听器需要重写的点击方法 : onGroupExpand
2、分组收起事件,相关类名与方法说明如下:
监听器类名 : OnGroupCollapseListener
设置监听器的方法 : setOnGroupCollapseListener
监听器需要重写的点击方法 : onGroupCollapse
3、分组点击事件,相关类名与方法说明如下:
监听器类名 : OnGroupClickListener
设置监听器的方法 : setOnGroupClickListener
监听器需要重写的点击方法 : onGroupClick
4、子项点击事件,相关类名与方法说明如下:
监听器类名 : OnChildClickListener
设置监听器的方法 : setOnChildClickListener
监听器需要重写的点击方法 : onChildClick
ExpandableListView适配器
ExpandableListAdapter是ExpandableListView的专用适配器,它并不继承自其他适配器。
下面是ExpandableListAdapter经常要重写的几个方法:
getGroupCount : 获取分组的个数。
getChildrenCount : 获取子项的个数。
getGroupView : 获取指定分组的视图。
getChildView : 获取指定子项的视图。
isChildSelectable : 判断子项是否允许选择。
ExpandableListView常见问题
ExpandableListView有时会发现子项不会响应点击事件,这可能是某个环节没有正确设置。要让子项目响应点击事件,需满足下面三个条件:
1、ExpandableListAdapter适配器的isChildSelectable方法要返回true;
2、ExpandableListView对象要注册监听器setOnChildClickListener,并重写onChildClick方法;
3、子项目中若有Button、EditText等默认占用焦点的控件,要去除焦点占用,即setFocusable和setFocusableInTouchMode设置为false;
下面是ExpandableListView的一个应用例子效果截图(电子邮箱):
下面是运用ExpandableListView的代码示例:
适配器代码
import java.util.ArrayList;
import com.example.exmfoldlist.R;
import com.example.exmfoldlist.bean.MailBox;
import com.example.exmfoldlist.bean.MailItem;
import android.content.Context;
import android.database.DataSetObserver;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
public class CustomExpandAdapter implements ExpandableListAdapter,OnGroupClickListener,OnChildClickListener {
private final static String TAG = "CustomExpandAdapter";
private LayoutInflater mInflater;
private Context mContext;
private ArrayList<MailBox> mBoxList;
public CustomExpandAdapter(Context context, ArrayList<MailBox> box_list) {
mInflater = LayoutInflater.from(context);
mContext = context;
mBoxList = box_list;
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
}
@Override
public int getGroupCount() {
return mBoxList.size();
}
@Override
public int getChildrenCount(int groupPosition) {
return mBoxList.get(groupPosition).mail_list.size();
}
@Override
public Object getGroup(int groupPosition) {
return mBoxList.get(groupPosition);
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return mBoxList.get(groupPosition).mail_list.get(childPosition);
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
ViewHolderBox holder = null;
if (convertView == null) {
holder = new ViewHolderBox();
convertView = mInflater.inflate(R.layout.list_box, null);
holder.iv_box = (ImageView) convertView.findViewById(R.id.iv_box);
holder.tv_box = (TextView) convertView.findViewById(R.id.tv_box);
holder.tv_count = (TextView) convertView.findViewById(R.id.tv_count);
convertView.setTag(holder);
} else {
holder = (ViewHolderBox) convertView.getTag();
}
MailBox box = mBoxList.get(groupPosition);
holder.iv_box.setImageResource(box.box_icon);
holder.tv_box.setText(box.box_title);
holder.tv_count.setText(box.mail_list.size()+"封邮件");
return convertView;
}
@Override
public View getChildView(final int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
ViewHolderMail holder = null;
if (convertView == null) {
holder = new ViewHolderMail();
convertView = mInflater.inflate(R.layout.list_mail, null);
holder.ck_mail = (CheckBox) convertView.findViewById(R.id.ck_mail);
holder.tv_date = (TextView) convertView.findViewById(R.id.tv_date);
convertView.setTag(holder);
} else {
holder = (ViewHolderMail) convertView.getTag();
}
MailItem item = mBoxList.get(groupPosition).mail_list.get(childPosition);
holder.ck_mail.setFocusable(false);
holder.ck_mail.setFocusableInTouchMode(false);
holder.ck_mail.setText(item.mail_title);
holder.ck_mail.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
MailBox box = mBoxList.get(groupPosition);
MailItem item = box.mail_list.get(childPosition);
String desc = String.format("您点击了%s的邮件,标题是%s", box.box_title, item.mail_title);
Toast.makeText(mContext, desc, Toast.LENGTH_LONG).show();
}
});
holder.tv_date.setText(item.mail_date);
return convertView;
}
//如果子条目需要响应点击事件,这里要返回true
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
@Override
public boolean areAllItemsEnabled() {
return true;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public void onGroupExpanded(int groupPosition) {
}
@Override
public void onGroupCollapsed(int groupPosition) {
}
@Override
public long getCombinedChildId(long groupId, long childId) {
return 0;
}
@Override
public long getCombinedGroupId(long groupId) {
return 0;
}
public final class ViewHolderBox {
public ImageView iv_box;
public TextView tv_box;
public TextView tv_count;
}
public final class ViewHolderMail {
public CheckBox ck_mail;
public TextView tv_date;
}
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
ViewHolderMail holder = (ViewHolderMail) v.getTag();
holder.ck_mail.setChecked(!(holder.ck_mail.isChecked()));
return true;
}
//如果返回true,就不会展示子列表
@Override
public boolean onGroupClick(ExpandableListView parent, View v,
int groupPosition, long id) {
String desc = String.format("您点击了%s", mBoxList.get(groupPosition).box_title);
Toast.makeText(mContext, desc, Toast.LENGTH_LONG).show();
return false;
}
}
调用的代码
import java.util.ArrayList;
import com.example.exmfoldlist.adapter.CustomExpandAdapter;
import com.example.exmfoldlist.bean.MailBox;
import com.example.exmfoldlist.bean.MailItem;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ExpandableListView;
public class ExpandActivity extends Activity {
private final static String TAG = "ExpandActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_expand);
ExpandableListView elv_list = (ExpandableListView) findViewById(R.id.elv_list);
final ArrayList<MailBox> box_list = new ArrayList<MailBox>();
box_list.add(new MailBox(R.drawable.mail_folder_inbox, "收件箱", getRecvMail()));
box_list.add(new MailBox(R.drawable.mail_folder_outbox, "发件箱", getSentMail()));
box_list.add(new MailBox(R.drawable.mail_folder_draft, "草稿箱", getDraftMail()));
box_list.add(new MailBox(R.drawable.mail_folder_recycle, "废件箱", getRecycleMail()));
CustomExpandAdapter adapter = new CustomExpandAdapter(this, box_list);
elv_list.setAdapter(adapter);
elv_list.setOnChildClickListener(adapter);
elv_list.setOnGroupClickListener(adapter);
elv_list.expandGroup(0); //默认展开第一个邮件夹
}
private ArrayList<MailItem> getRecvMail() {
ArrayList<MailItem> mail_list = new ArrayList<MailItem>();
mail_list.add(new MailItem("这里是收件箱呀1", "2016年3月25日"));
mail_list.add(new MailItem("这里是收件箱呀2", "2016年3月20日"));
mail_list.add(new MailItem("这里是收件箱呀3", "2016年3月24日"));
mail_list.add(new MailItem("这里是收件箱呀4", "2016年3月21日"));
mail_list.add(new MailItem("这里是收件箱呀5", "2016年3月23日"));
return mail_list;
}
private ArrayList<MailItem> getSentMail() {
ArrayList<MailItem> mail_list = new ArrayList<MailItem>();
mail_list.add(new MailItem("邮件发出去了吗1", "2016年3月25日"));
mail_list.add(new MailItem("邮件发出去了吗2", "2016年3月24日"));
mail_list.add(new MailItem("邮件发出去了吗3", "2016年3月21日"));
mail_list.add(new MailItem("邮件发出去了吗4", "2016年3月23日"));
mail_list.add(new MailItem("邮件发出去了吗5", "2016年3月20日"));
return mail_list;
}
private ArrayList<MailItem> getDraftMail() {
ArrayList<MailItem> mail_list = new ArrayList<MailItem>();
mail_list.add(new MailItem("暂时放在草稿箱吧1", "2016年3月24日"));
mail_list.add(new MailItem("暂时放在草稿箱吧2", "2016年3月21日"));
mail_list.add(new MailItem("暂时放在草稿箱吧3", "2016年3月25日"));
mail_list.add(new MailItem("暂时放在草稿箱吧4", "2016年3月20日"));
mail_list.add(new MailItem("暂时放在草稿箱吧5", "2016年3月23日"));
return mail_list;
}
private ArrayList<MailItem> getRecycleMail() {
ArrayList<MailItem> mail_list = new ArrayList<MailItem>();
mail_list.add(new MailItem("啊啊啊,怎么被删除了1", "2016年3月21日"));
mail_list.add(new MailItem("啊啊啊,怎么被删除了2", "2016年3月23日"));
mail_list.add(new MailItem("啊啊啊,怎么被删除了3", "2016年3月25日"));
mail_list.add(new MailItem("啊啊啊,怎么被删除了4", "2016年3月20日"));
mail_list.add(new MailItem("啊啊啊,怎么被删除了5", "2016年3月24日"));
return mail_list;
}
}
折叠式布局FoldingLayout
ExpandableListView对于一般场景的折叠式列表已经够用了,可是它的UI风格略显呆板,如果我们想来个显示特效,比如加上折叠展开的动画,那最好还是自己写个折叠式列表控件。
FoldingLayout便是这样一个开源的折叠式布局控件,它实现了像折纸那样折叠展开和折叠收起的动画。下面是FoldingLayout的常用方法说明:
setFoldFactor : 设置折叠的因子。0表示收起,1表示展开。
setOrientation : 设置折叠的方向。VERTICAL表示垂直方向,HORIZONTAL表示水平方向。
setNumberOfFolds : 设置折叠的页数。
FoldingLayout也提供了折叠事件的监听,相关类名与方法说明如下:
监听器类名 : OnFoldListener
设置监听器的方法 : setFoldListener
监听器需要重写的点击方法 :
onStartFold : 开始折叠时触发。
onFoldingState : 折叠状态变化时触发。
onEndFold : 结束折叠时触发。
下面是运用FoldingLayout的代码示例:
import com.example.exmfoldlist.util.MetricsUtil;
import com.example.exmfoldlist.view.FoldingLayout;
import com.example.exmfoldlist.view.OnFoldListener;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.AccelerateInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
public class FoldingActivity extends Activity {
private String TAG_ARROW = "service_arrow";
private String TAG_ITEM = "service_item";
private View mBottomView;
private LinearLayout mTrafficLayout, mLifeLayout, mMedicalLayout, mLiveLayout, mPublicLayout;
private RelativeLayout mTrafficBarLayout, mLifeBarLayout, mMedicalBarLayout, mLiveBarLayout, mPublicBarLayout;
private FoldingLayout mTrafficFoldingLayout, mLifeFoldingLayout, mMedicalFoldingLayout, mLiveFoldingLayout, mPublicFoldingLayout;
private final int FOLD_ANIMATION_DURATION = 1000;
private int mNumberOfFolds = 3;
private Handler mHandler = new Handler();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_folding);
mTrafficLayout = (LinearLayout) findViewById(R.id.traffic_layout);
mLifeLayout = (LinearLayout) findViewById(R.id.life_layout);
mMedicalLayout = (LinearLayout) findViewById(R.id.medical_layout);
mLiveLayout = (LinearLayout) findViewById(R.id.live_layout);
mPublicLayout = (LinearLayout) findViewById(R.id.public_layout);
mTrafficBarLayout = (RelativeLayout) findViewById(R.id.traffic_bar_layout);
mLifeBarLayout = (RelativeLayout) findViewById(R.id.life_bar_layout);
mMedicalBarLayout = (RelativeLayout) findViewById(R.id.medical_bar_layout);
mLiveBarLayout = (RelativeLayout) findViewById(R.id.live_bar_layout);
mPublicBarLayout = (RelativeLayout) findViewById(R.id.public_bar_layout);
mTrafficFoldingLayout = ((FoldingLayout) mTrafficLayout.findViewWithTag(TAG_ITEM));
mLifeFoldingLayout = ((FoldingLayout) mLifeLayout.findViewWithTag(TAG_ITEM));
mMedicalFoldingLayout = ((FoldingLayout) mMedicalLayout.findViewWithTag(TAG_ITEM));
mLiveFoldingLayout = ((FoldingLayout) mLiveLayout.findViewWithTag(TAG_ITEM));
mPublicFoldingLayout = ((FoldingLayout) mPublicLayout.findViewWithTag(TAG_ITEM));
mBottomView = findViewById(R.id.bottom_view);
initFoldingLayout(mTrafficFoldingLayout, mTrafficBarLayout, mTrafficLayout, mLifeLayout);
initFoldingLayout(mLifeFoldingLayout, mLifeBarLayout, mLifeLayout, mMedicalLayout);
initFoldingLayout(mMedicalFoldingLayout, mMedicalBarLayout, mMedicalLayout, mLiveLayout);
initFoldingLayout(mLiveFoldingLayout, mLiveBarLayout, mLiveLayout, mPublicLayout);
initFoldingLayout(mPublicFoldingLayout, mPublicBarLayout, mPublicLayout, mBottomView);
setBarEnabled(false);
mHandler.postDelayed(mDefaultFold, 150);
}
private void initFoldingLayout(final FoldingLayout foldingLayout, View bar, final View thisView, final View nextView) {
foldingLayout.setVisibility(View.INVISIBLE);
bar.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handleAnimation(v, foldingLayout, thisView, nextView);
}
});
foldingLayout.setNumberOfFolds(mNumberOfFolds);
animateFold(foldingLayout, 100);
setMarginToTop(1, nextView);
}
private void setBarEnabled(boolean enabled) {
mTrafficBarLayout.setEnabled(enabled);
mLifeBarLayout.setEnabled(enabled);
mMedicalBarLayout.setEnabled(enabled);
mLiveBarLayout.setEnabled(enabled);
mPublicBarLayout.setEnabled(enabled);
mTrafficFoldingLayout.setFoldFactor(!enabled?0.0f:1.0f);
mLifeFoldingLayout.setFoldFactor(!enabled?0.0f:1.0f);
mMedicalFoldingLayout.setFoldFactor(!enabled?0.0f:1.0f);
mLiveFoldingLayout.setFoldFactor(!enabled?0.0f:1.0f);
mPublicFoldingLayout.setFoldFactor(!enabled?0.0f:1.0f);
}
private Runnable mDefaultFold = new Runnable() {
@Override
public void run() {
setBarEnabled(true);
handleAnimation(mTrafficBarLayout, mTrafficFoldingLayout, mTrafficLayout, mLifeLayout);
}
};
private void handleAnimation(final View bar, final FoldingLayout foldingLayout, View parent, final View nextParent) {
foldingLayout.setVisibility(View.VISIBLE);
final ImageView arrow = (ImageView) parent.findViewWithTag(TAG_ARROW);
foldingLayout.setFoldListener(new OnFoldListener() {
@Override
public void onStartFold(float foldFactor) {
bar.setClickable(true);
arrow.setBackgroundResource(R.drawable.service_arrow_up);
resetMarginToTop(foldingLayout, foldFactor, nextParent);
}
@Override
public void onFoldingState(float foldFactor, float foldDrawHeight) {
bar.setClickable(false);
resetMarginToTop(foldingLayout, foldFactor, nextParent);
}
@Override
public void onEndFold(float foldFactor) {
bar.setClickable(true);
arrow.setBackgroundResource(R.drawable.service_arrow_down);
resetMarginToTop(foldingLayout, foldFactor, nextParent);
}
});
animateFold(foldingLayout, FOLD_ANIMATION_DURATION);
}
private void resetMarginToTop(View view, float foldFactor, View nextParent) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) nextParent.getLayoutParams();
lp.topMargin =(int)( - view.getMeasuredHeight() * foldFactor) + MetricsUtil.dip2px(FoldingActivity.this, 10);
nextParent.setLayoutParams(lp);
}
private void setMarginToTop(float foldFactor, View nextParent) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) nextParent.getLayoutParams();
lp.topMargin =(int)( - MetricsUtil.dip2px(FoldingActivity.this, 135) * foldFactor) + MetricsUtil.dip2px(FoldingActivity.this, 10);
nextParent.setLayoutParams(lp);
}
public void animateFold(FoldingLayout foldLayout, int duration) {
float foldFactor = foldLayout.getFoldFactor();
ObjectAnimator animator = ObjectAnimator.ofFloat(foldLayout,
"foldFactor", foldFactor, foldFactor > 0 ? 0 : 1);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setRepeatCount(0);
animator.setDuration(duration);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
}
}
点此查看Android开发笔记的完整目录