自定义布局,堆叠布局来袭!
2015-12-28 22:15
537 查看
最近爱上写博客了,一来可以巩固自己的学习成果,二来可以帮助有需要的人。好了闲话就到这里,开始进入今天的正题。
这篇博客是上篇博客点击打开链接的后续,上篇博客跟大家一起时间了仿大街app的拖动删除或者收藏的效果,如果还没看的话,可以去看看。当然没看的话也不影响看这篇博客。为了给不看上篇博客的人提供方便,我在这里先贴下今天我们要完成的效果。
还是那句话,感兴趣的看官可以看下,顺便帮我顶下,谢谢啦。不感兴趣的大腿请绕道呀。好了,开工!
2.对子view的处理(旋转,透明度变换等)
3.为自定义布局提供setAdapter注入数据的支持
4.为自定义布局提供view重用的机制
先定义一些变量,作用的话注释写得挺清楚了,不清楚的话看后面的使用就懂了。这里简单说一下mContentWith和mContentHeight这两个是自定义的属性,用于设置内容区域的宽度和高度的。以factor结尾的变量则是控制item的透明度或者旋转相关的梯度值。
嗯,构造方法是好了,然后什么时候给StackLayout添加数据呢,当然是在setAdapter的时候了,这里我们自定义一下我们为StackLayout提供的StackLayoutAdapter
嗯,简直不要太简单,用过listview或者gridview的都知道,这基本是仿造的android提供的adpter来写的。继续回到自定义布局的setAdapter方法
这个方法我们要判断一下数据的长度,如果数据的长度大于2,那么我们默认只加载两条数据,至于为什么是2呢,人不2,那跟咸鱼还有什么区别?
可以看到,我们在for循环中调用addViewToFirst,这个方法最终会调用makeAndAddView,其实也就是获得一个view,然后将view添加到StackLayout的第一个位置。
来,接着看makeAndAddView方法。
到这里可能有人会问,妈的你每个方法里面的逻辑就那么一点,写那么多方法干嘛?这里我想说的是,虽然没几行代码,但是毕竟实现的功能不一样,以不同的功能来划分方法,我觉得代码结构还是挺清晰的,也有利于以后的扩展。个人见解。然后再来看看是如何获得一个view的。
得到view之后就为view设置一些事件啦。
代码逻辑真是炒鸡简单,就是根据用户手指滑动的距离改变view的透明度啊,旋转的角度啊什么的,然后再手指抬起的时候判断移动的距离是是否超过了限定的距离(mLimitTranslateX),如果超过了,则删除这个view。这里再看一下移除view的逻辑。
完啦,对啊,我们的自定义布局这样就完啦,然后我们来看看MainActivity的逻辑
嗯,效果还是挺满意的,不过好像跟我们原来的那些东西相比少了些什么?我们左滑忽略图片或者右滑的时候那个感兴趣的图片哪去了?嗯,那我们来为它添加下。
(-。-;)呃..,怎么添加呢?写到StackLayout的initEvent里面去?肯定不行啦,这样这个自定义布局就只适用于我们这个例子。直接拿到vi
d426
ew然后设置onTouchListener?这样肯定也不行了,那我们原来设置的旋转和透明度变化的效果就被覆盖了。我们的目的肯定是要适用于任何情况,那么设置特定效果的这种事肯定是得我们在不改变StackLayout控件的情况下来进行改造了。
嗯,我的解决方法是设置一个回调,在view触发onTouch的时候回调方法,并且把view传回来,用户可以在回调方法里设置自己的逻辑,嗯,那我们开始着手实现吧。
在StackLayout里面定义一个接口,里面就一个回调方法,当触发的时候,把这些参数传进去,这里我把移动的距离distanceX也传进去了,虽然这个值可以利用event计算得到,但是为了防止重复计算,我还是把这个值传进去好。然后为其提供一个设置listener的方法。接着我们就得在initEvent里面进行相应的修改了。
基本也没怎么修改,也就是在move和up的前面判断一下listener是否为空,如果不为空,则回调方法。然后看一下我们如何设置这个回调。回到我们的MainActivity。
前面的代码就不贴了,就贴下增加的代码,相信不用过多的解释了吧。注释也有。然后看一下效果。
到这里我们的效果就跟我们开头的效果一样了。各位看官了,满足了吗?顶下我的博客呗。
什么?你还不满意?卧槽?好吧,其实我也不满意,接下来我们为其添加类似listview的复用功能。
我想大家都知道ListView有个复用view的机制,当view移出屏幕的时候会将这个移出屏幕的view进行复用,这里我们不过多讨论ListView的这个特性。我们接着实现我们的代码。
首先不用想,我们肯定得有个容器来存放我们废弃掉的view,这里我使用LinkedList。接下来哪里需要有复用view的逻辑呢?答案显而易见,肯定是在remoeView的时候将view添加到废弃list中,addView的时候从废弃list中获取view了。那么看看removeViewWithAnim的新版本
第四行,判断当前list是否有缓存view,有的话,返回最后一个,没有的话,返回null。
嗯,大概就更改这么点,然后我们要如何使用它呢?用过ListView的肯定知道ViewHolder吧,省得每次都去findViewById了。
ViewHolder我就写成StackLayoutAdater的静态内部类了,大概是这么个样子。
然后我们就要在MainActivity中使用改进后的布局了。代码如下
我就贴有改动过的代码就好。也就是setAdapter部分的逻辑。首先,判断是否有复用的view,没有的话就新inflate一个,然后创建一个ViewHolder并与view绑定。如果有复用的view,即convertview不为null,则直接取出viewholder。然后后面爱干嘛干嘛。最终把view给返回去。
这种是ListView最简单的复用view的写法了,对这部分还比较模糊的还是先学学ListView吧。好了,试试效果先。
嗯,效果上看没什么变化,不过看一下打印的log。
有人会问,咦,不是默认只加载两个布局吗,怎么inflate了3次。因为我们removeView的时候是带动画的,所以会有延迟,导致第三个view加载的时候,第一个view还没有被回收,所以会再inflate一次,之后就都是复用view了。怎样?杠杠的吧。
我就不继续对这个布局发表其他博客了,其实还有很多改造的地方,比如可以支持几种viewType什么等等。。
嗯,这篇博客到此结束,谢谢大家的收看,顶个回复呗。。
这篇博客是上篇博客点击打开链接的后续,上篇博客跟大家一起时间了仿大街app的拖动删除或者收藏的效果,如果还没看的话,可以去看看。当然没看的话也不影响看这篇博客。为了给不看上篇博客的人提供方便,我在这里先贴下今天我们要完成的效果。
还是那句话,感兴趣的看官可以看下,顺便帮我顶下,谢谢啦。不感兴趣的大腿请绕道呀。好了,开工!
主要功能分析
1.自定义布局2.对子view的处理(旋转,透明度变换等)
3.为自定义布局提供setAdapter注入数据的支持
4.为自定义布局提供view重用的机制
界面分析
这个上篇博客已经写过了,不懂的可以自己去看下。大概就是不断动态的为FrameLayout添加和删除view,item布局没啥好说,主要就是自定义形状的imageview(圆形图片、弧边的矩形)。功能实现
自定义布局
类似这种堆叠效果的话,我们就不用按照传统的自定义布局的步骤,先继承ViewGroup,然后重写onMeasure和onLayout什么的了,android已经有提供一个有类似这种效果的原生布局了,嗯没错,就是FrameLayout。嗯分析到这里我觉得后面就挺简单了,下面看代码。自定义布局 StackLayout
private StackLayoutAdapter mAdapter; private int mContentWidth = 350;//内容区域的宽度 dp private int mContentHeight = 470;//内容区域的高度 dp private float mRotateFactor;//控制item旋转范围 private double mItemAlphaFactor;//控制item透明度变化范围 private int mLimitTranslateX = 100;//限制移动距离,当超过这个距离的时候,删除该item public StackLayout(Context context) { this(context, null); } public StackLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public StackLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.StackLayout); mContentWidth = t.getDimensionPixelSize(R.styleable.StackLayout_contentWidth, ScreenUtils .dp2px(mContentWidth, getContext())); mContentHeight = t.getDimensionPixelSize(R.styleable.StackLayout_contentHeight, ScreenUtils.dp2px(mContentHeight, getContext())); int screenWidth = ScreenUtils.getScreenWidth(getContext()); mRotateFactor = 60 * 1.0f / screenWidth; //左滑,透明度最少到0.1f mItemAlphaFactor = 0.9 * 1.0f / screenWidth / 2; }
先定义一些变量,作用的话注释写得挺清楚了,不清楚的话看后面的使用就懂了。这里简单说一下mContentWith和mContentHeight这两个是自定义的属性,用于设置内容区域的宽度和高度的。以factor结尾的变量则是控制item的透明度或者旋转相关的梯度值。
嗯,构造方法是好了,然后什么时候给StackLayout添加数据呢,当然是在setAdapter的时候了,这里我们自定义一下我们为StackLayout提供的StackLayoutAdapter
StackLayout的适配器StackLayoutAdapter
public abstract class StackLayoutAdapter<T> { private Context mContext; private List<T> mDatas; private int mCurrentIndex;//目前数据集读到的下标 public StackLayoutAdapter(Context context, List<T> datas) { this.mContext = context; this.mDatas = datas; } public int getCount() { return mDatas.size(); } public T getItem(int pos) { return mDatas.get(pos); } public int getCurrentIndex() { return mCurrentIndex; } public void setCurrentIndex(int index) { this.mCurrentIndex = index; } public abstract View getView(int pos, View convertView, ViewGroup parent);
嗯,简直不要太简单,用过listview或者gridview的都知道,这基本是仿造的android提供的adpter来写的。继续回到自定义布局的setAdapter方法
setAdapter方法
public void setAdapter(StackLayoutAdapter adapter) { this.mAdapter = adapter; //最多加载两条数据 int itemCount = adapter.getCount(); int loadCount = itemCount > 2 ? 2 : itemCount; for (int i = 0; i < loadCount; i++) { addViewToFirst(); } } /** * 将item添加到最后的位置 */ public void addViewToFirst() { makeAndAddView(0); }
这个方法我们要判断一下数据的长度,如果数据的长度大于2,那么我们默认只加载两条数据,至于为什么是2呢,人不2,那跟咸鱼还有什么区别?
可以看到,我们在for循环中调用addViewToFirst,这个方法最终会调用makeAndAddView,其实也就是获得一个view,然后将view添加到StackLayout的第一个位置。
来,接着看makeAndAddView方法。
makeAndAddView方法
private void makeAndAddView(int pos) { if (mAdapter.getCurrentIndex() == mAdapter.getCount() - 1) { return;//没有更多数据 } View item = obtainView(mAdapter.getCurrentIndex()); addView(item, pos); //增加数据集的下标 mAdapter.setCurrentIndex(mAdapter.getCurrentIndex() + 1); }首先先判断数据当前下标是否已经到数据的末尾了,如果到了直接返回,否则,调用obtainView方法获得一个view,获得view后将view添加到StackLayout中指定的位置去,这里根据上下文,传入的是0这个位置,也就是布局的第一个位置。添加后更新下数据集的下标。
到这里可能有人会问,妈的你每个方法里面的逻辑就那么一点,写那么多方法干嘛?这里我想说的是,虽然没几行代码,但是毕竟实现的功能不一样,以不同的功能来划分方法,我觉得代码结构还是挺清晰的,也有利于以后的扩展。个人见解。然后再来看看是如何获得一个view的。
obtainView方法
private View obtainView(int pos) { //加载布局 View item = LayoutInflater.from(getContext()).inflate(R.layout.stack_item, null); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(mContentWidth, mContentHeight, Gravity .CENTER_HORIZONTAL); item.setLayoutParams(lp); item = mAdapter.getView(pos, item, this); //初始化事件 initEvent(item); return item; }跟上篇博客差不都,还是调用的inflate方法来加载一个布局,然后设置一下它的参数。嗯,这里看下mAdapter.getView方法,这是一个抽象方法,当你在为StackLayout设置Adapter的时候是需要重写这个方法的。
得到view之后就为view设置一些事件啦。
initEvent方法
private void initEvent(final View item) { //设置item的重心,主要是旋转的中心 item.setPivotX(item.getLayoutParams().width / 2); item.setPivotY(item.getLayoutParams().height * 2); item.setOnTouchListener(new View.OnTouchListener() { float touchX, distanceX;//手指按下时的坐标以及手指在屏幕移动的距离 @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: touchX = event.getRawX(); break; case MotionEvent.ACTION_MOVE: distanceX = event.getRawX() - touchX; item.setRotation(distanceX * mRotateFactor); //alpha scale 1~0.1 //item的透明度为从1到0.1 item.setAlpha(1 - (float) Math.abs(mItemAlphaFactor * distanceX)); break; case MotionEvent.ACTION_UP: if (Math.abs(distanceX) > mLimitTranslateX) { //移除view removeViewWithAnim(getChildCount() - 1, distanceX < 0); addViewToFirst(); } else { //复位 item.setRotation(0); item.setAlpha(1); } break; } return true; } }); }
代码逻辑真是炒鸡简单,就是根据用户手指滑动的距离改变view的透明度啊,旋转的角度啊什么的,然后再手指抬起的时候判断移动的距离是是否超过了限定的距离(mLimitTranslateX),如果超过了,则删除这个view。这里再看一下移除view的逻辑。
removeViewWithAnim方法
public View removeViewWithAnim(int pos, boolean isLeft) { final View view = getChildAt(pos); view.animate() .alpha(0) .rotation(isLeft ? -90 : 90) .setDuration(400).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { removeView(view); if (getChildCount() == 0)//如果只剩一条item的时候 { Toast.makeText(getContext(), "已是最后一页...", Toast.LENGTH_SHORT).show(); } } }); return view; }为了不让用户显得突兀,我们这里要有一个淡出的效果,当然要根据左滑还是右滑设置淡出的方向,然后在动画结束的时候在removeView,并且如果没有更多数据的时候,给用户打印个toast。
完啦,对啊,我们的自定义布局这样就完啦,然后我们来看看MainActivity的逻辑
MainActivity
private StackLayout mContainer; private double mItemIvAlphaFactor;//控制item上面的图片的透明度变化范围 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContainer = (StackLayout) findViewById(R.id.flContainer); mContainer.setAdapter(new StackLayoutAdapter<User>(this, GenerateData.getDatas()) { @Override public View getView(int pos, View convertView, ViewGroup parent) { if (convertView != null) { User user = getItem(pos); // convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout // .stack_item, null); ImageView roundAvatar = (ImageView) convertView.findViewById(R.id.roundAvatar); ImageView blurAvatar = (ImageView) convertView.findViewById(R.id.blurAvatar); CatImageLoader.getInstance().loadImage(user.getAvater(), blurAvatar); CatImageLoader.getInstance().loadImage(user.getAvater(), roundAvatar); TextView tvUsername = (TextView) convertView.findViewById(R.id.tvUsername); TextView tvSchool = (TextView) convertView.findViewById(R.id.tvSchool); TextView tvMajor = (TextView) convertView.findViewById(R.id.tvMajor); TextView tvEntranceTime = (TextView) convertView.findViewById(R.id .tvEntranceTime); TextView tvSkill = (TextView) convertView.findViewById(R.id.tvSkill); final ImageView ivIgnore = (ImageView) convertView.findViewById(R.id.ivIgnore); final ImageView ivInterested = (ImageView) convertView.findViewById(R.id .ivInterested); tvUsername.setText(user.getName()); tvSchool.setText(user.getSchool()); tvMajor.setText(user.getMajor() + " | " + user.getSchoolLevel()); tvEntranceTime.setText(user.getEntranceTime()); tvSkill.setText("装逼 吹牛逼"); } return convertView; } }); }这种类型的代码是不是炒鸡眼熟?跟listview很像吧?不过这个是我们自己实现的,是不是有点小激动,反正作为一个小鸟,我觉得是有点小激动。ok,来看看我们目前为止的效果。
嗯,效果还是挺满意的,不过好像跟我们原来的那些东西相比少了些什么?我们左滑忽略图片或者右滑的时候那个感兴趣的图片哪去了?嗯,那我们来为它添加下。
(-。-;)呃..,怎么添加呢?写到StackLayout的initEvent里面去?肯定不行啦,这样这个自定义布局就只适用于我们这个例子。直接拿到vi
d426
ew然后设置onTouchListener?这样肯定也不行了,那我们原来设置的旋转和透明度变化的效果就被覆盖了。我们的目的肯定是要适用于任何情况,那么设置特定效果的这种事肯定是得我们在不改变StackLayout控件的情况下来进行改造了。
嗯,我的解决方法是设置一个回调,在view触发onTouch的时候回调方法,并且把view传回来,用户可以在回调方法里设置自己的逻辑,嗯,那我们开始着手实现吧。
OnTouchEffectListener接口
public interface onTouchEffectListener { void onTouchEffect(View item, MotionEvent event, float distanceX); }
public void setOnTouchEffectListener(onTouchEffectListener listener) { this.mOnTouchEffectListener = listener; }
在StackLayout里面定义一个接口,里面就一个回调方法,当触发的时候,把这些参数传进去,这里我把移动的距离distanceX也传进去了,虽然这个值可以利用event计算得到,但是为了防止重复计算,我还是把这个值传进去好。然后为其提供一个设置listener的方法。接着我们就得在initEvent里面进行相应的修改了。
修改后的initEvent方法
private void initEvent(final View item) { //设置item的重心,主要是旋转的中心 item.setPivotX(item.getLayoutParams().width / 2); item.setPivotY(item.getLayoutParams().height * 2); item.setOnTouchListener(new View.OnTouchListener() { float touchX, distanceX;//手指按下时的坐标以及手指在屏幕移动的距离 @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: touchX = event.getRawX(); break; case MotionEvent.ACTION_MOVE: distanceX = event.getRawX() - touchX; if (mOnTouchEffectListener != null) mOnTouchEffectListener.onTouchEffect(item, event, distanceX); item.setRotation(distanceX * mRotateFactor); //alpha scale 1~0.1 //item的透明度为从1到0.1 item.setAlpha(1 - (float) Math.abs(mItemAlphaFactor * distanceX)); break; case MotionEvent.ACTION_UP: if (mOnTouchEffectListener != null) mOnTouchEffectListener.onTouchEffect(item, event, distanceX); if (Math.abs(distanceX) > mLimitTranslateX) { //移除view removeViewWithAnim(getChildCount() - 1, distanceX < 0); addViewToFirst(); } else { //复位 item.setRotation(0); item.setAlpha(1); } break; } return true; } }); }
基本也没怎么修改,也就是在move和up的前面判断一下listener是否为空,如果不为空,则回调方法。然后看一下我们如何设置这个回调。回到我们的MainActivity。
MainActivity
mContainer.setOnTouchEffectListener(new StackLayout.onTouchEffectListener() { @Override public void onTouchEffect(View item, MotionEvent event, float distanceX) { ImageView ivIgnore = (ImageView) item.findViewById(R.id.ivIgnore); ImageView ivInterested = (ImageView) item.findViewById(R.id .ivInterested); switch (event.getAction()) { case MotionEvent.ACTION_MOVE: if (distanceX < 0)//如果为左滑 { //显示忽略图标,隐藏感兴趣图标 ivIgnore.setVisibility(View.VISIBLE); ivInterested.setVisibility(View.GONE); ivIgnore.setAlpha((float) (Math.abs(distanceX) * mItemIvAlphaFactor)); } else//如果为右滑 { //显示感兴趣图标,隐藏忽略图标 ivIgnore.setVisibility(View.GONE); ivInterested.setVisibility(View.VISIBLE); ivInterested.setAlpha((float) (distanceX * mItemIvAlphaFactor)); } break; case MotionEvent.ACTION_UP: if (Math.abs(distanceX) < mContainer.getLimitTranslateX()) { //复位 ivIgnore.setAlpha(1.0f); ivInterested.setAlpha(1.0f); ivIgnore.setVisibility(View.GONE); ivInterested.setVisibility(View.GONE); } break; } } });
前面的代码就不贴了,就贴下增加的代码,相信不用过多的解释了吧。注释也有。然后看一下效果。
到这里我们的效果就跟我们开头的效果一样了。各位看官了,满足了吗?顶下我的博客呗。
什么?你还不满意?卧槽?好吧,其实我也不满意,接下来我们为其添加类似listview的复用功能。
我想大家都知道ListView有个复用view的机制,当view移出屏幕的时候会将这个移出屏幕的view进行复用,这里我们不过多讨论ListView的这个特性。我们接着实现我们的代码。
private LinkedList<View> mScrapViews = new LinkedList<>();
首先不用想,我们肯定得有个容器来存放我们废弃掉的view,这里我使用LinkedList。接下来哪里需要有复用view的逻辑呢?答案显而易见,肯定是在remoeView的时候将view添加到废弃list中,addView的时候从废弃list中获取view了。那么看看removeViewWithAnim的新版本
removeViewWithAnim新版本
public View removeViewWithAnim(final View view, boolean isLeft) { // final View view = getChildAt(pos); view.animate() .alpha(0) .rotation(isLeft ? -90 : 90) .setDuration(400).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { removeView(view); //移除view后将view添加到我们的废弃view的list中 resetItem(view);//记得重置状态,否则复用的时候会看不到view mScrapViews.add(view); if (getChildCount() == 0)//如果只剩一条item的时候 { Toast.makeText(getContext(), "已是最后一页...", Toast.LENGTH_SHORT).show(); } } }); return view; }没什么改变,就是在removeView后,重置一下状态。这一步很重要,因为view此时的透明度为0,是看不见的,如果不重置,将会导致你复用view的时候看不到。接着就把view添加到废弃list中了。我们看一眼resetItem
resetItem
private void resetItem(View item) { item.setRotation(0); item.setAlpha(1); }不多说了。接着看我们添加view时的逻辑
obtainView
private View obtainView(int pos) { //先尝试从废弃缓存中取出view View scrapView = mScrapViews.size() > 0 ? mScrapViews.removeLast() : null; View item = mAdapter.getView(pos, scrapView, this); if (item != scrapView) { //代表view布局变化了,inflate了新的布局 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(mContentWidth, mContentHeight, Gravity.CENTER_HORIZONTAL); item.setLayoutParams(lp); //初始化事件 initEvent(item); } return item; }
第四行,判断当前list是否有缓存view,有的话,返回最后一个,没有的话,返回null。
嗯,大概就更改这么点,然后我们要如何使用它呢?用过ListView的肯定知道ViewHolder吧,省得每次都去findViewById了。
ViewHolder我就写成StackLayoutAdater的静态内部类了,大概是这么个样子。
StackLayoutAdapter.ViewHolder
public static class ViewHolder { public ImageView roundAvatar; public ImageView blurAvatar; public TextView tvUsername; public TextView tvSchool; public TextView tvMajor; public TextView tvEntranceTime; public TextView tvSkill; public ImageView ivIgnore; public ImageView ivInterested; public ViewHolder(ImageView roundAvatar, ImageView blurAvatar, TextView tvUsername, TextView tvSchool, TextView tvMajor, TextView tvEntranceTime, TextView tvSkill, ImageView ivIgnore, ImageView ivInterested) { this.roundAvatar = roundAvatar; this.blurAvatar = blurAvatar; this.tvUsername = tvUsername; this.tvSchool = tvSchool; this.tvMajor = tvMajor; this.tvEntranceTime = tvEntranceTime; this.tvSkill = tvSkill; this.ivIgnore = ivIgnore; this.ivInterested = ivInterested; } }
然后我们就要在MainActivity中使用改进后的布局了。代码如下
MainActivity最终效果
mContainer.setAdapter(new StackLayoutAdapter<User>(this, GenerateData.getDatas()) { @Override public View getView(int pos, View convertView, ViewGroup parent) { ViewHolder viewHolder; User user = getItem(pos); if (convertView == null) { convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout .stack_item, null); ImageView roundAvatar = (ImageView) convertView.findViewById(R.id.roundAvatar); ImageView blurAvatar = (ImageView) convertView.findViewById(R.id.blurAvatar); TextView tvUsername = (TextView) convertView.findViewById(R.id.tvUsername); TextView tvSchool = (TextView) convertView.findViewById(R.id.tvSchool); TextView tvMajor = (TextView) convertView.findViewById(R.id.tvMajor); TextView tvEntranceTime = (TextView) convertView.findViewById(R.id .tvEntranceTime); TextView tvSkill = (TextView) convertView.findViewById(R.id.tvSkill); ImageView ivIgnore = (ImageView) convertView.findViewById(R.id.ivIgnore); ImageView ivInterested = (ImageView) convertView.findViewById(R.id .ivInterested); viewHolder = new ViewHolder(roundAvatar, blurAvatar, tvUsername, tvSchool, tvMajor, tvEntranceTime, tvSkill, ivIgnore, ivInterested); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.ivIgnore.setVisibility(View.GONE); viewHolder.ivInterested.setVisibility(View.GONE); CatImageLoader.getInstance().loadImage(user.getAvater(), viewHolder.blurAvatar); CatImageLoader.getInstance().loadImage(user.getAvater(), viewHolder.roundAvatar); viewHolder.tvUsername.setText(user.getName()); viewHolder.tvSchool.setText(user.getSchool()); viewHolder.tvMajor.setText(user.getMajor() + " | " + user.getSchoolLevel()); viewHolder.tvEntranceTime.setText(user.getEntranceTime()); viewHolder.tvSkill.setText("装逼 吹牛逼"); return convertView; } });
我就贴有改动过的代码就好。也就是setAdapter部分的逻辑。首先,判断是否有复用的view,没有的话就新inflate一个,然后创建一个ViewHolder并与view绑定。如果有复用的view,即convertview不为null,则直接取出viewholder。然后后面爱干嘛干嘛。最终把view给返回去。
这种是ListView最简单的复用view的写法了,对这部分还比较模糊的还是先学学ListView吧。好了,试试效果先。
嗯,效果上看没什么变化,不过看一下打印的log。
有人会问,咦,不是默认只加载两个布局吗,怎么inflate了3次。因为我们removeView的时候是带动画的,所以会有延迟,导致第三个view加载的时候,第一个view还没有被回收,所以会再inflate一次,之后就都是复用view了。怎样?杠杠的吧。
总结
到这里就接近尾声了,我们也比较规范的实现了一个属于我们的布局了虽然比较简单,但是我们也是跟随者谷歌的脚步着实地装了一次逼,毕竟为了与国际接轨,我们采用setAdapter的形式注入数据,也采用了仿listview的复用机制。当然,跟listview的复用机制相比,这个简直,呵呵。。我就不继续对这个布局发表其他博客了,其实还有很多改造的地方,比如可以支持几种viewType什么等等。。
嗯,这篇博客到此结束,谢谢大家的收看,顶个回复呗。。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories