做程序开发,基础很重要。同样是拧螺丝人家拧出来的可以经久不坏,你拧出来的遇到点风浪就开始颤抖,可见基本功的重要性。此系列,专门收录一些看似基础,但是没那么简单的小细节,同时提供权威解决方案。喜欢的同志们点个赞就是对我最大的鼓励!先行谢过!
网上可能有一些其他文章,提供了解决方案,但是要么就是没有提供可运行demo
,要么就是demo不够纯粹
,让人探索起来受到其他代码因素的影响,无法专注于当前这个知识点(比如,我只是想了解Activity
的生命周期,你把生命周期探究的过程混入到一个很复杂的大杂烩Demo
中,让人一眼就没有了阅读Demo代码
的欲望),所以我觉得有必要做一个专题,用最纯粹
的方式展示一个坑
的解决方案.
ViewPager。老牌系统控件了,坑当然也有不少,今天先说第一个。
Github地址:https://github.com/18598925736/ViewPagerKeng
建议下载源码之后对照阅读
很简单,activity_main.xml
里面 一个ViewPager
然后:MainActivity.java
很简单的一个ViewPager
的adapter
使用。
package study.hank.com.viewpager;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewPager vp = findViewById(R.id.vp);
List list = new ArrayList<>();
list.add(new Data("1"));
list.add(new Data("2"));
list.add(new Data("3"));
vp.setAdapter(new MyPagerAdapter(this, list));
}
class MyPagerAdapter extends PagerAdapter {
private Context context;
private List dataList;
public MyPagerAdapter(Context context, List dataList) {
this.context = context;
this.dataList = dataList;
}
@Override
public int getCount() {
return dataList == null ? 0 : dataList.size();
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
View root = LayoutInflater.from(context).inflate(R.layout.vp_item, container, false);
TextView tv = root.findViewById(R.id.tv);
tv.setText(dataList.get(position).text);
container.addView(root);
return root;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return view == o;
}
}
private class Data {
String text;
public Data(String text) {
this.text = text;
}
}
}
但是请注意:adapte
r的item
布局如下:
这个布局的根节点宽高为: wrap_content 以及 200dp
原本我的想法是,子view设置了自适应宽,和固定高度,那么viewPager作为父容器,也应该呈现相应的宽高。
但是打脸了。
WTF??
你居然默认充满了???
思考一下,决定一个View
宽高的是什么? View的onMeasure
方法。
于是看看ViewPager
的onMeasure
,一看吓一跳:
WTF? 难道我使用ViewPager就只能充满父容器么?万一我一个容器里要放多个ViewPager,那不是还要包一层布局?太low了。
既然
ViewPager
的测量这么坑,那就不要用他自己的onMeasure
了。我们帮它测。
package study.hank.com.viewpager;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
public class MyViewPager extends ViewPager {
public MyViewPager(@NonNull Context context) {
super(context);
}
public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int hSpec, wSpec;
int finalH = 0, finalW = 0;
final int count = getChildCount();
View child;
//我原本想直接使用子view测量之后的宽高,然后发现,尼玛,ViewPager根本就没有针对考虑子View的WrapContent
// for (int i = 0; i < count; i++) {
// //算出所有子view的最大宽度
// child = getChildAt(i);
// int thisH = child.getMeasuredHeight();
// int thisW = child.getMeasuredWidth();
// finalH = finalH > thisH ? finalH : thisH;
// finalW = finalW > thisW ? finalW : thisW;
// }
//
// hSpec = MeasureSpec.makeMeasureSpec(finalH, MeasureSpec.EXACTLY);
// wSpec = MeasureSpec.makeMeasureSpec(finalW, MeasureSpec.EXACTLY);
// 那没办法了,那就再对子view进行一次常规测量
// 坑并不只是 ViewPager没有对子进行测量,而是,它对于子的测量,也是使用的默认matchParent··使得子view充满父容器
// 那我自己来考虑子view的LayoutParam来测量一次
for (int i = 0; i < count; i++) {
child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
int thisH = child.getMeasuredHeight();
int thisW = child.getMeasuredWidth();
finalH = finalH > thisH ? finalH : thisH;
finalW = finalW > thisW ? finalW : thisW;
Log.d("MeasureSpec ", "finalH:" + finalH + "/finalW:" + finalW);
}
hSpec = MeasureSpec.makeMeasureSpec(finalH, MeasureSpec.EXACTLY);//这里的模式。。emmm
wSpec = MeasureSpec.makeMeasureSpec(finalW, MeasureSpec.EXACTLY);
setMeasuredDimension(wSpec, hSpec);
}
}
继承ViewPager
,重写onMeasure
,再次给他setMeasuredDimension
.
我们自己给他的测量,考虑子View的宽高,并且得出子view的最大宽高,设置给它。
使用我们自己的MyViewPager
替换它。
现在的效果:
我们给子
view
设置的宽高生效了
案例确实很简单,但是如果我们遇到问题,不知道如何思考,只知道去找度娘的话,那永远是个
lowB
。或者 你用一层布局,把ViewPager
包起来,那更是lowB
。作为一个高级开(码)发(农),要学会从根本上解决问题,从源码层寻找解决方案。
例如今天这个案例,我的目的是,当ViewPager的item 是自适应宽高的时候,我怎么让外部的ViewPager
也呈现出自适应呢? 它原本的宽高就是占满父容器。从源码中找到原因,是它的onMeasure
出现 了奇葩的逻辑,
虽然不知道谷歌大佬为什么这么写,囧。重写onMeasure之后,解决了问题,满足了需求。
但是,如果有其他需求呢?比如,宽度要求充满,高度要自适应子item。或者高度要自适应,宽度要是精确值。原理相同,按照需求重写onMeasure
即可。
今天小小案例,抛砖引玉,希望帮助有缘人打开思路,不要在CV
的道路上越走越黑。。。(像是对我自己说的 - -!)
如果你看到了这里,觉得文章写得不错就点个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。
有一句老话说的好:**“比你优秀的对手在学习,你的仇人在磨刀,你的闺蜜在减肥,隔壁老王在练腰,我们必须不断学习,否则我们将被学习者超越。”**当然一个人学习是枯燥的,还需要一个良好的学习氛围,因此我组建了一个学习交流探讨的社群,欢迎大家一起来交流探讨共同进步。还有一些收集整理的资料,感兴趣的可以来一起学习,共同进步!
针对Android开发的同行,这边给大家整理了一些资料,其中分享内容包括但不限于**【高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术】**希望能帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也是可以分享给身边好友一起学习的!
希望读到这的您能转发分享和关注一下我,以后还会更新技术干货,谢谢您的支持!
点赞+关注,关注微信公众号【Android开发之家】获取小编为大家收录的进阶资料和面试题库
转发+点赞,关注微信公众号【Android开发之家】获取小编为大家收录的进阶资料和面试题库
Android架构师之路很漫长,一起共勉吧!