android 自定义ViewGroup之浪漫求婚
*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
1、最终效果
有木有发现还是很小清新的感觉
2、看整体效果这是一个scrollView,滑动时每个子view都有一个或多个动画效果,但是如果我们直接给每个子view加上动画去实现这个需求就太low了,而且也不利于扩展,所以这里将会设计一套框架,使别人能很方便的使用我们定义的控件。
3、首先看看我们是怎么使用自己设计的这个控件的
<scrollviewgroup.lly.com.scrollviewgroup.lib.DiscrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:discrollve="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<scrollviewgroup.lly.com.scrollviewgroup.lib.DiscrollViewContent
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<ImageView
android:layout_width="300dp"
android:layout_height="180dp"
android:layout_gravity="center"
discrollve:discrollve_alpha="true"
discrollve:discrollve_translation="fromLeft|fromBottom"
android:src="@drawable/cheese1" />
...
</scrollviewgroup.lly.com.scrollviewgroup.lib.DiscrollViewContent>
</scrollviewgroup.lly.com.scrollviewgroup.lib.DiscrollView>
看discrollve:discrollve_alpha="true"
discrollve:discrollve_translation="fromLeft|fromBottom"
这里我们给系统控件加上自定义属性,这样当别人用我们的控件,简直不要太爽。
不过大家有没有发现这是系统控件哎,你就这么随随便便的给它加个属性,它认识么 不报错你就谢天谢地了 还让它工作,想的美。
带着这个疑惑,我们先来看看系统的ViewGroup.java类是怎么做的。
一般我们在代码中给布局动态添加子控件的时候都会用到addView这个方法
这里我们就跟踪这个方法,最后发现他们会调用到ViewGroup的addview方法
public void addView(View child, int index) {
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
有没有发现这里这里最后的params是怎么来的 不就是子控件的params么。
而addView(child, index, params); 最后会调用addViewInner
下面我们看下addViewInner是怎么做的
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
...
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
...
addInArray(child, index);
// tell our children
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
...
onViewAdded(child);
...
}
代码还是比较多的,只关注对我们有用的片段
首先它会调用checkLayoutParams(params)
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p != null;
}
如果不等于空就会调用就调用generateLayoutParams
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return p;
}
继续执行
if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
看到上面的checkLayoutParams和generateLayoutParams方法都比较简单而且是protected的 所以应该是给子类实现的,我们看一个viewgroup的子类 LinearLayout是怎么做的
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LinearLayout.LayoutParams;
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LinearLayout.LayoutParams(getContext(), attrs);
}
看到这里就在想我们是不是也可以这么做呢,那当然是可以的 系统都可以了还有什么问题,
接下来我们的大波代码来袭了
public class DiscrollViewContent extends LinearLayout {
public DiscrollViewContent(Context context) {
super(context);
setOrientation(VERTICAL);
}
public DiscrollViewContent(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
}
public DiscrollViewContent(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
}
/**
* 重写addView
* @param child
* @param index
* @param params
*/
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(asDiscrollvable(child,(MyLayoutParams)params), index, params);
}
private View asDiscrollvable(View child, MyLayoutParams params) {
if(!isDiscrollvable(params)){
return child;
}
DiscrollvableView discrollvableChild = new DiscrollvableView(getContext());
discrollvableChild.setDiscrollveAlpha(params.mDiscrollveAlpha);
discrollvableChild.setDiscrollveTranslation(params.mDiscrollveTranslation);
discrollvableChild.setDiscrollveScaleX(params.mDiscrollveScaleX);
discrollvableChild.setDiscrollveScaleY(params.mDiscrollveScaleY);
discrollvableChild.setDiscrollveThreshold(params.mDiscrollveThreshold);
discrollvableChild.setDiscrollveFromBgColor(params.mDiscrollveFromBgColor);
discrollvableChild.setDiscrollveToBgColor(params.mDiscrollveToBgColor);
discrollvableChild.addView(child);
return discrollvableChild;
}
/**
* 判断是否是我们定义的LayoutParams
* @param lp
* @return
*/
private boolean isDiscrollvable(MyLayoutParams lp) {
return lp.mDiscrollveAlpha ||
lp.mDiscrollveTranslation != -1 ||
lp.mDiscrollveScaleX ||
lp.mDiscrollveScaleY ||
(lp.mDiscrollveFromBgColor != -1 && lp.mDiscrollveToBgColor != -1);
}
/**
* 重写checkLayoutParams
* @param p
* @return
*/
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MyLayoutParams;
}
/**
* 重写generateDefaultLayoutParams
* @return
*/
@Override
protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
return new MyLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
/**
* 重写generateLayoutParams
* @param attrs
* @return
*/
@Override
public LinearLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MyLayoutParams(getContext(), attrs);
}
/**
* 重写generateLayoutParams
* @param p
* @return
*/
@Override
protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new MyLayoutParams(p.width, p.height);
}
/**
* 自定义LinearLayout.LayoutParams
*/
class MyLayoutParams extends LinearLayout.LayoutParams {
private int mDiscrollveFromBgColor;
private int mDiscrollveToBgColor;
private float mDiscrollveThreshold;
public boolean mDiscrollveAlpha;
public boolean mDiscrollveScaleX;
public boolean mDiscrollveScaleY;
private int mDiscrollveTranslation;
public MyLayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
try {
mDiscrollveAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_alpha, false);
mDiscrollveScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleX, false);
mDiscrollveScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleY, false);
mDiscrollveTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_translation, -1);
mDiscrollveThreshold = a.getFloat(R.styleable.DiscrollView_LayoutParams_discrollve_threshold, 0.0f);
mDiscrollveFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_fromBgColor, -1);
mDiscrollveToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_toBgColor, -1);
} finally {
a.recycle();
}
}
public MyLayoutParams(int width, int height) {
super(width, height);
}
}
}
上面这大段代码主要就做了我们上面分析的系统空间
首先继承LinearLayout,
重写了addView,generateLayoutParams,checkLayoutParams
并自定义了一个MyLayoutParams继承自LinearLayout.LayoutParams
在addview的时候我们首先对child进行下处理,判断子view中是否有我们定义属性,没有的话,就用它自己,有的话,我们在外层包一个FrameLayout,让他执行动画,他的子view也将跟着执行。
好了 框架的设计部分完成了,
下面就是动画的实现了
首先看我们的scrollView是怎么做的
public class DiscrollView extends ScrollView {
private DiscrollViewContent mContent;
public DiscrollView(Context context) {
super(context);
}
public DiscrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public DiscrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setupFirstView();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if(getChildCount() != 1) {
throw new IllegalStateException("Discrollview must host one child.");
}
View content = getChildAt(0);
if(!(content instanceof DiscrollViewContent)) {
throw new IllegalStateException("Discrollview must host a DiscrollViewContent.");
}
mContent = (DiscrollViewContent) content;
if(mContent.getChildCount() < 2) {
throw new IllegalStateException("Discrollview must have at least 2 children.");
}
}
private void setupFirstView() {
View first = mContent.getChildAt(0);
if(first!=null){
first.getLayoutParams().height = getHeight();
}
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
onScrollChanged(t);
}
private void onScrollChanged(int top) {
int scrollViewHeight = getHeight();
int scrollViewBottom = getAbsoluteBottom();
int scrollViewHalfHeight = scrollViewHeight / 2;
for(int index = 1;index<mContent.getChildCount();index++){
View child = mContent.getChildAt(index);
if(!(child instanceof DiscrollVable)){
continue;
}
DiscrollVable discrollvable = (DiscrollVable) child;
int discrollvableTop = child.getTop();
int discrollvableHeight = child.getHeight();
int discrollvableAbsoluteTop = discrollvableTop - top;
//这个view的下半部分
if(scrollViewBottom - child.getBottom() < discrollvableHeight+scrollViewHalfHeight){
//子view显示的时候执行
if(discrollvableAbsoluteTop <= scrollViewHeight){
int visibleGap = scrollViewHeight - discrollvableAbsoluteTop;
discrollvable.onDiscrollve(clamp(visibleGap / (float)discrollvableHeight,0.0f,1.0f));
}else {
//子view还没显示的时候
discrollvable.onResetDiscrollve();
}
}else{
if(discrollvableAbsoluteTop <= scrollViewHalfHeight){
int visibleGap = scrollViewHalfHeight - discrollvableAbsoluteTop;
discrollvable.onDiscrollve(clamp(visibleGap / (float)discrollvableHeight,0.0f,1.0f));
}else{
discrollvable.onResetDiscrollve();
}
}
}
}
private float clamp(float value, float max, float min) {
return Math.max(Math.min(value,min),max);
}
public int getAbsoluteBottom() {
View last = getChildAt(getChildCount()-1);
if(last == null){
return 0;
}
return last.getBottom();
}
}
主要就是在滑动的时候 把滑动的百分比传给接口 ,具体由接口的实现类来执行
而实现接口的类就是我们上面的那个FrameLayout。
@Override
public void onDiscrollve(float ratio) {
if(ratio >= mDiscrollveThreshold) {
ratio = withThreshold(ratio);
float ratioInverse = 1 - ratio;
if(mDiscrollveAlpha) {
setAlpha(ratio);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_BOTTOM)) {
setTranslationY(mHeight * ratioInverse);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_TOP)) {
setTranslationY(-mHeight * ratioInverse);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_LEFT)) {
setTranslationX(-mWidth * ratioInverse);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_RIGHT)) {
setTranslationX(mWidth * ratioInverse);
}
if(mDiscrollveScaleX) {
setScaleX(ratio);
}
if(mDiscrollveScaleY) {
setScaleY(ratio);
}
if(mDiscrollveFromBgColor != -1 && mDiscrollveToBgColor != -1) {
setBackgroundColor((Integer) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));
}
}
}
@Override
public void onResetDiscrollve() {
if(mDiscrollveAlpha) {
setAlpha(0.0f);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_BOTTOM)) {
setTranslationY(mHeight);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_TOP)) {
setTranslationY(-mHeight);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_LEFT)) {
setTranslationX(-mWidth);
}
if(isDiscrollveTranslationFrom(TRANSLATION_FROM_RIGHT)) {
setTranslationX(mWidth);
}
if(mDiscrollveScaleX) {
setScaleX(0.0f);
}
if(mDiscrollveScaleY) {
setScaleY(0.0f);
}
if(mDiscrollveFromBgColor != -1 && mDiscrollveToBgColor != -1) {
setBackgroundColor(mDiscrollveFromBgColor);
}
}
代码贴的太多了 底部将给出源码
可以看出 每个类都不是很大,当用户要用的时候只要 在xml中引用我们的控件,就可以实现这个效果,而且他要别的效果的话同样只要在xml中配置就好。
android 自定义ViewGroup之浪漫求婚的更多相关文章
- android自定义viewgroup之我也玩瀑布流
先看效果图吧, 继上一篇<android自定义viewgroup实现等分格子布局>中实现的布局效果,这里稍微有些区别,每个格子的高度不规则,就是传说的瀑布流布局,一般实现这种效果,要么用第 ...
- Android自定义ViewGroup
视图分类就两类,View和ViewGroup.ViewGroup是View的子类,ViewGroup可以包含所有的View(包括ViewGroup),View只能自我描绘,不能包含其他View. 然而 ...
- Android自定义ViewGroup,实现自动换行
学习<Android开发艺术探索>中自定义ViewGroup章节 自定义ViewGroup总结的知识点 一.自定义ViewGroup中,onMeasure理解 onMeasure(int ...
- android自定义viewgroup实现等分格子布局
先上效果图: 实现这样的效果: 一般的思路就是,直接写布局文件,用LinearLayout 嵌套多层子LinearLayout,然后根据权重layout_weight可以达到上面的效果 还有就是利用g ...
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这里我简单说明一下用自定义ViewGroup来实现. 实现方法:我们自定义一个ViewGroup实现左右滑动, ...
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]
http://blog.csdn.net/jj120522/article/details/8095852 示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这 ...
- Android自定义ViewGroup(四、打造自己的布局容器)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51500304 本文出自:[openXu的博客] 目录: 简单实现水平排列效果 自定义Layo ...
- android自定义viewgroup初步之一----抽屉菜单
转载请注明出处 http://blog.csdn.net/wingichoy/article/details/47832151 几天前在慕课网上看到鸿洋老师的 自定义卫星菜单,感觉很有意思,于是看完视 ...
- Android 自定义ViewGroup手把手教你实现ArcMenu
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907 逛eoe发现这样的UI效果,感觉很不错,后来知道github上有这 ...
随机推荐
- 用js来实现那些数据结构06(队列)
其实队列跟栈有很多相似的地方,包括其中的一些方法和使用方式,只是队列使用了与栈完全不同的原则,栈是后进先出原则,而队列是先进先出(First In First Out). 一.队列 队列是一种特 ...
- 关于python 使用腾讯云OCR 通用印刷体识别
腾讯的python SDK没有通用印刷体识别,所以参考了别人识别网上图片的方式:https://www.cnblogs.com/semishigure/p/7690789.html 但是咱们使用的基本 ...
- c++类与对象(1)——构造,复制构造函数
CPP的类与对象: 当我们定义一个基本类型变量时,方法如下: int p; #定义一个int类型变量 bool b; #定义一个bool类型变量 float c ; #定义一个float类型变量 显然 ...
- Struts2--标签tag
在Struts2框架中提供了一套标签库,可以与struts2无缝结合. 数据标签a.action.bean.date.debug.i18n.include.param.property.push.se ...
- 【分享】几篇关于Repository 相关的讨论、提问、文章
一.引入 最近在了解DDD,对于里面Repository 有点疑问和关注.闲来无事,去找了一些文章,来补补.在这里分享出来给大家.文章大多数都是英文的,见谅哈. 二.推荐列表 2.1 Filters ...
- [SHOI 2008]Debt 循环的债务
Description 题库链接 A 欠 B \(x_1\) 元, B 欠 C \(x_2\) 元, C 欠 A \(x_3\) 元.现每人手上各有若干张 100,50,20,10,5,1 钞票.问至 ...
- [Sdoi2009]Elaxia的路线
Description 最近,Elaxia和w**的关系特别好,他们很想整天在一起,但是大学的学习太紧张了,他们 必须合理地安排两个人在一起的时间.Elaxia和w**每天都要奔波于宿舍和实验室之间, ...
- CTSC&APIO2017
CTSC Day -1 因为越发感到自己与dalao们之间姿势水平的差距,本来打算再多学些姿势,但被老师叫去做noi,于是花了一两周的时间做完了noi2011~2015,也学到了一些奇怪姿势,还是挺有 ...
- hdu 5445 多重背包
Food Problem Time Limit: 3000/2000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)To ...
- Safari 3D transform变换z-index层级渲染异常
(猛戳来源:http://www.zhangxinxu.com/wordpress/?p=5569)