android 自定义ViewGroup之浪漫求婚

    xiaoxiao2021-09-17  60


    *本篇文章已授权微信公众号 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中配置就好。

    github

    转载请注明原文地址: https://ju.6miu.com/read-677670.html

    最新回复(0)