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上有这 ...
随机推荐
- Java知识体系纲要
最近一段时间,把Java主要涉及到的大概念都大致学习了一遍,为了让自己能够更好地形成对Java知识体系的整体把握,先把学过的知识点添加到自己画的思维导图上. 整个Java知识体系的划分,我自己主要将它 ...
- 自行实现高性能MVC WebAPI
wcf虽然功能多.扩展性强但是也面临配置忒多,而且restful的功能相当怪异,并且目前没法移植.asp.net core虽然支持webapi,但是功能也相对繁多.配置复杂.就没有一个能让码农们安安心 ...
- How to preview html file in our browser at sublime text?
sublime preview html.md open In Browser what should we do if we want to preview html file in our bro ...
- 推送本地项目至Github遇到的问题以及解决办法记录
在把本地新项目推送至GitHub仓库时的大致流程和步骤,首先现在GitHub上面新建一个项目,复制该项目的 带.git 后缀的地址,比如 git@github.com:XXX/XXX.git 然后在本 ...
- [HEOI2015]兔子与樱花
Description 很久很久之前,森林里住着一群兔子.有一天,兔子们突然决定要去看樱花.兔子们所在森林里的樱花树很特殊.樱花树由n个树枝分叉点组成,编号从0到n-1,这n个分叉点由n-1个树枝连接 ...
- [HNOI2011]任务调度
题目描述 有 N 个任务和两台机器 A 与 B.每个任务都需要既在机器 A 上执行,又在机器 B 上执行, 第 i 个任务需要在机器 A 上执行时间 Ai,且需要在机器 B 上执行时间 Bi.最终的目 ...
- codeforces 651C Watchmen
Watchmen are in a danger and Doctor Manhattan together with his friend Daniel Dreiberg should warn t ...
- UVALive - 3530:Martian Mining
dp 可以发现,对于(i,j),要么把它运上去,那么把它运到左边,枚举一下即可 #include<cstdio> #include<cstdlib> #include<a ...
- ●BZOJ 3172 [Tjoi2013]单词
题链: http://www.lydsy.com/JudgeOnline/problem.php?id=3172 题解: 把单词逐个接起来,中间用互不相同的字符连接,并记录下每个单词的首字母在串中的位 ...
- 2015 多校联赛 ——HDU5360(贪心+优先队列)
Sample Input 4 8 4 1 3 2 2 1 0 3 5 3 6 4 2 1 7 6 8 3 3 2 0 5 0 3 6 4 5 2 7 7 6 7 6 8 2 2 3 3 3 0 0 2 ...