Android属性动画与自定义View——实现vivo x6更新系统的动画效果

晚上好,现在是凌晨两点半,然后我还在写代码。电脑里播放着《凌晨两点半》,晚上写代码,脑子更清醒,思路更清晰。

今天聊聊属性动画和自定义View搭配使用,前面都讲到自定义View和属性动画,但是一起用的还是不多,刚巧今晚手机提示我更新系统,我看到那个更新的动画还不错,仔细的分析了一下,于是我也决定写一个,不是一模一样的,但是效果和原理是一样的。

先看看图:

这是一张静态的图,这里有三个波浪线,当下载完之后,波浪线会往上活动,一直到消失。

所以难点也是在这个波浪线上。这个波浪线类似于一个水波纹,也有很多的大神写过水波纹的效果,所以当我们知道这个是什么的时候,就比较容易实现这个功能了。

这一次,先看看用到了什么关键的技术。

首先要介绍的,就是高中的时候学过的余弦函数,还记得表达式吗?

y = Asin(wx+b)+h

这个公式里:w影响周期,A影响振幅,h影响y位置,b为初相;

接下来就看看这一次用到了哪些变量:

// y = Asin(wx+b)+h
    private static final float STRETCH_FACTOR_A = 50;
    private static final int OFFSET_Y = 0;
    // 第一条水波移动速度
    private static final int TRANSLATE_X_SPEED_ONE = 7;
    // 第二条水波移动速度
    private static final int TRANSLATE_X_SPEED_TWO = 5;
    // 第三条水波移动速度
    private static final int TRANSLATE_X_SPEED_Three = 10;
    private float mCycleFactorW;

    private int mTotalWidth, mTotalHeight;
    private float[] mYPositions;
    private float[] mResetOneYPositions;
    private float[] mResetTwoYPositions;
    private float[] mResetThreeYPositions;
    private int mXOffsetSpeedOne;
    private int mXOffsetSpeedTwo;
    private int mXOffsetSpeedThree;

    private int mXOneOffset;
    private int mXTwoOffset;
    private int mXThreeOffset;

    private int height = 1200;

    private Paint mWavePaint, mWavePaint2,mWavePaint3;
    private DrawFilter mDrawFilter;

都是常规的,像画笔、高宽等等。

然后看看初始化的操作,在构造函数里面:

    public WaterRipple(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 将dp转化为px,用于控制不同分辨率上移动速度基本一致
        mXOffsetSpeedOne = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_ONE);
        mXOffsetSpeedTwo = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_TWO);
        mXOffsetSpeedThree = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_Three);
        // 初始绘制波纹的画笔
        mWavePaint = new Paint();
        // 去除画笔锯齿
        mWavePaint.setAntiAlias(true);
        // 设置风格为实线
        mWavePaint.setStyle(Style.FILL);
        // 设置画笔颜色
        mWavePaint.setColor(Color.parseColor("#ffffff"));
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
                | Paint.FILTER_BITMAP_FLAG);

        // 初始绘制波纹的画笔
        mWavePaint2 = new Paint();
        // 去除画笔锯齿
        mWavePaint2.setAntiAlias(true);
        // 设置风格为实线
        mWavePaint2.setStyle(Style.FILL);
        // 设置画笔颜色
        mWavePaint2.setColor(Color.parseColor("#eeeeee"));

        // 初始绘制波纹的画笔
        mWavePaint3 = new Paint();
        // 去除画笔锯齿
        mWavePaint3.setAntiAlias(true);
        // 设置风格为实线
        mWavePaint3.setStyle(Style.FILL);
        // 设置画笔颜色
        mWavePaint3.setColor(Color.parseColor("#ffffff"));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 记录下view的宽高
        mTotalWidth = w;
        mTotalHeight = h;
        // 用于保存原始波纹的y值
        mYPositions = new float[mTotalWidth];
        // 用于保存波纹一的y值
        mResetOneYPositions = new float[mTotalWidth];
        // 用于保存波纹二的y值
        mResetTwoYPositions = new float[mTotalWidth];
        // 用于保存波纹三的y值
        mResetThreeYPositions = new float[mTotalWidth];
        // 将周期定为view总宽度
        mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);

        // 根据view总宽度得出所有对应的y值
        for (int i = 0; i < mTotalWidth; i++) {
            mYPositions[i] = (float) (STRETCH_FACTOR_A
                    * Math.sin(mCycleFactorW * i) + OFFSET_Y);
        }
    }

这个初始化倒是不用怎么解释,重点说一下下面的方法:

private void resetPositonY() {
        // mXOneOffset代表当前第一条水波纹要移动的距离
        int yOneInterval = mYPositions.length - mXOneOffset;
        // 使用System.arraycopy方式重新填充第一条波纹的数据
        // 第一个参数就是源数组,第二个参数是要复制的源数组中的起始位置,
        // 第三个参数是目标数组,第四个参数是要复制到的目标数组的起始位置,第五个参数是要复制的元素的长度
        System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0,
                yOneInterval);
        System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval,
                mXOneOffset);

        int yTwoInterval = mYPositions.length - mXTwoOffset;

        System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
                yTwoInterval);
        System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval,
                mXTwoOffset);

        int yThreeInterval = mYPositions.length - mXThreeOffset;

        System.arraycopy(mYPositions, mXThreeOffset, mResetThreeYPositions, 0,
                yThreeInterval);
        System.arraycopy(mYPositions, 0, mResetThreeYPositions, yThreeInterval,
                mXThreeOffset);

    }

这里面用到了一个System.arraycopy的方法,这个在我们平时的时候可能用得比较少,但是确实非常好用,建议大家可以看看这个的具体用法。我这里也是尽量的写满注释,以便更好的理解。

接下来就是最关键的onDraw方法,代码如下:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 从canvas层面去除绘制时锯齿
        canvas.setDrawFilter(mDrawFilter);
        resetPositonY();
        for (int i = 0; i < mTotalWidth; i++) {
            // 绘制第一条水波纹
            canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - height,
                    i, mTotalHeight, mWavePaint);

            // 绘制第二条水波纹
            canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - height
                    + 200, i, mTotalHeight, mWavePaint2);

            // 绘制第三条水波纹
            canvas.drawLine(i, mTotalHeight - mResetThreeYPositions[i] - height
                    + 400, i, mTotalHeight, mWavePaint3);

        }

        // 改变两条波纹的移动点
        mXOneOffset += mXOffsetSpeedOne;
        mXTwoOffset += mXOffsetSpeedTwo;
        mXThreeOffset += mXOffsetSpeedThree;

        // 如果已经移动到结尾处,则重新记录
        if (mXOneOffset >= mTotalWidth) {
            mXOneOffset = 0;
        }
        if (mXTwoOffset > mTotalWidth) {
            mXTwoOffset = 0;
        }
        if (mXThreeOffset > mTotalWidth) {
            mXThreeOffset = 0;
        }
    }

我这里绘制了三个波浪线,以及处理了一些在绘制中的数据。

最后暴露给外面一个方法,用于动态的调整高度。


    public void setHeight(int height) {
        this.height = height;
        postInvalidate();
    }

最重要的View已经绘制出来了,下面看看其他的代码:

首先是布局文件,activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#12ADFF"
    tools:context=".MainActivity" >

    <com.ltl.waterripple.WaterRipple
        android:id="@+id/hehe"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <RelativeLayout
        android:id="@+id/relativi"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content" >

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_margin="5dip"
                    android:text="设置"
                    android:textColor="#ffffff"
                    android:textSize="20sp" />
            </RelativeLayout>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:gravity="center"
                android:text="Funtouch OS"
                android:textColor="#ffffff"
                android:textSize="40sp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:gravity="center"
                android:text="Power For Android"
                android:textColor="#ffffff"
                android:textSize="20sp" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/content_linear"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:orientation="vertical"
            android:padding="20dip" >

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:text="更新版本:vivo rev 1.14.1(81MB)" />

            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:layout_marginBottom="10dip"
                android:layout_marginTop="10dip"
                android:background="#eeeeee" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="系统"
                android:textStyle="bold" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="更新了部分第三方应用图标" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="优化了系统与一些第三方应用的兼容性" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="优化了应用安装体验" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="发现威胁会有弹窗提醒" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="10dip"
                android:background="#eeeeee"
                android:padding="1px" >

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="#ffffff"
                    android:clickable="true"
                    android:gravity="center"
                    android:onClick="onClick"
                    android:padding="5dip"
                    android:text="下载并安装"
                    android:textSize="20sp"
                    android:textStyle="bold" />
            </LinearLayout>
        </LinearLayout>

        <LinearLayout
            android:id="@+id/progress_linear"
            android:layout_width="match_parent"
            android:layout_height="200dip"
            android:layout_alignParentBottom="true"
            android:gravity="center|bottom"
            android:orientation="vertical"
            android:padding="20dip"
            android:visibility="gone" >

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="25sp"
                android:text="当前版本:2.354785" />

            <TextView
                android:id="@+id/text_state"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="状态" />

            <ProgressBar
                android:id="@+id/progressBar1"
                style="?android:attr/progressBarStyleHorizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </RelativeLayout>

</RelativeLayout>

这一次的布局写的比较多,也是用到了多层的嵌套。所以在阅读的时候要很注意细节。

接下来是MainActivity.java:

public class MainActivity extends Activity {
    private int height2 = 1200;
    private int size = 0;
    private ProgressBar bar;
    private int height;
    private WaterRipple ripple;
    private TextView text_state;
    private LinearLayout layout1, layout2;
    private RelativeLayout layout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        ripple = (WaterRipple) findViewById(R.id.hehe);
        bar = (ProgressBar) findViewById(R.id.progressBar1);
        layout1 = (LinearLayout) findViewById(R.id.content_linear);
        layout2 = (LinearLayout) findViewById(R.id.progress_linear);
        text_state = (TextView) findViewById(R.id.text_state);
        layout = (RelativeLayout) findViewById(R.id.relativi);
        WindowManager wm = this.getWindowManager();
        height = wm.getDefaultDisplay().getHeight();
        height2 = (int)(height*0.7);
        ripple.setHeight(height2);
        bar.setMax(81);
    }

    Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {

            switch (msg.what) {
            case 1:
                if (size < 81) {
                    bar.setProgress(size);
                    text_state.setText("下载进度:"
                            + (float) (((float) (size) / 81) * 100) + "%");
                    size++;

                } else {
                    startAnim();
                    layout2.setVisibility(View.GONE);
                }
                break;
            case 2:
                ripple.setHeight(height2);

                ViewGroup.LayoutParams params = layout.getLayoutParams();
                params.height = height - height2;
                layout.setLayoutParams(params);
                height2++;
                break;
            case 3:
                ObjectAnimator animator = ObjectAnimator.ofFloat(ripple,
                        "alpha", 1f, 0f,0.5f);
                ObjectAnimator animator2 = ObjectAnimator.ofFloat(layout,
                        "alpha", 1f, 0f,0.5f);
                AnimatorSet animatorSet = new AnimatorSet();
                animatorSet.playTogether(animator, animator2);
                animatorSet.setDuration(300);
                animatorSet.start();
                break;
            default:
                break;
            }

        };
    };

    public void onClick(View v) {
        new Thread(new Runnable() {
            public void run() {
                while (size < 81) {
                    SystemClock.sleep(100);
                    handler.sendEmptyMessage(1);
                }
            }
        }).start();
        layout1.setVisibility(View.GONE);
        layout2.setVisibility(View.VISIBLE);
    }

    public void startAnim() {
        new Thread(new Runnable() {
            public void run() {
                while (height2 < height) {
                    SystemClock.sleep(100);
                    handler.sendEmptyMessage(2);
                }
                handler.sendEmptyMessage(3);
            }
        }).start();
    }

}

这里我有两个线程,主要是控制下载的进度展示和水波纹效果。具体的参数,使用者可以根据自己的需要,再作调整。

这里面也用了一个工具类DensityUtil.java:

public class DensityUtil {

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }
}

还有那个控件WaterRipple.java:

public class WaterRipple extends View {

    // y = Asin(wx+b)+h
    private static final float STRETCH_FACTOR_A = 50;
    private static final int OFFSET_Y = 0;
    // 第一条水波移动速度
    private static final int TRANSLATE_X_SPEED_ONE = 7;
    // 第二条水波移动速度
    private static final int TRANSLATE_X_SPEED_TWO = 5;
    // 第三条水波移动速度
    private static final int TRANSLATE_X_SPEED_Three = 10;
    private float mCycleFactorW;

    private int mTotalWidth, mTotalHeight;
    private float[] mYPositions;
    private float[] mResetOneYPositions;
    private float[] mResetTwoYPositions;
    private float[] mResetThreeYPositions;
    private int mXOffsetSpeedOne;
    private int mXOffsetSpeedTwo;
    private int mXOffsetSpeedThree;

    private int mXOneOffset;
    private int mXTwoOffset;
    private int mXThreeOffset;

    private int height = 1200;

    private Paint mWavePaint, mWavePaint2,mWavePaint3;
    private DrawFilter mDrawFilter;

    public WaterRipple(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 将dp转化为px,用于控制不同分辨率上移动速度基本一致
        mXOffsetSpeedOne = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_ONE);
        mXOffsetSpeedTwo = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_TWO);
        mXOffsetSpeedThree = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_Three);
        // 初始绘制波纹的画笔
        mWavePaint = new Paint();
        // 去除画笔锯齿
        mWavePaint.setAntiAlias(true);
        // 设置风格为实线
        mWavePaint.setStyle(Style.FILL);
        // 设置画笔颜色
        mWavePaint.setColor(Color.parseColor("#ffffff"));
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
                | Paint.FILTER_BITMAP_FLAG);

        // 初始绘制波纹的画笔
        mWavePaint2 = new Paint();
        // 去除画笔锯齿
        mWavePaint2.setAntiAlias(true);
        // 设置风格为实线
        mWavePaint2.setStyle(Style.FILL);
        // 设置画笔颜色
        mWavePaint2.setColor(Color.parseColor("#eeeeee"));

        // 初始绘制波纹的画笔
        mWavePaint3 = new Paint();
        // 去除画笔锯齿
        mWavePaint3.setAntiAlias(true);
        // 设置风格为实线
        mWavePaint3.setStyle(Style.FILL);
        // 设置画笔颜色
        mWavePaint3.setColor(Color.parseColor("#ffffff"));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 从canvas层面去除绘制时锯齿
        canvas.setDrawFilter(mDrawFilter);
        resetPositonY();
        for (int i = 0; i < mTotalWidth; i++) {

            // 减400只是为了控制波纹绘制的y的在屏幕的位置,大家可以改成一个变量,然后动态改变这个变量,从而形成波纹上升下降效果
            // 绘制第一条水波纹
            canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - height,
                    i, mTotalHeight, mWavePaint);

            // 绘制第二条水波纹
            canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - height
                    + 200, i, mTotalHeight, mWavePaint2);

            // 绘制第三条水波纹
            canvas.drawLine(i, mTotalHeight - mResetThreeYPositions[i] - height
                    + 400, i, mTotalHeight, mWavePaint3);

        }

        // 改变两条波纹的移动点
        mXOneOffset += mXOffsetSpeedOne;
        mXTwoOffset += mXOffsetSpeedTwo;
        mXThreeOffset += mXOffsetSpeedThree;

        // 如果已经移动到结尾处,则重头记录
        if (mXOneOffset >= mTotalWidth) {
            mXOneOffset = 0;
        }
        if (mXTwoOffset > mTotalWidth) {
            mXTwoOffset = 0;
        }
        if (mXThreeOffset > mTotalWidth) {
            mXThreeOffset = 0;
        }

        // 引发view重绘,一般可以考虑延迟20-30ms重绘,空出时间片

    }

    private void resetPositonY() {
        // mXOneOffset代表当前第一条水波纹要移动的距离
        int yOneInterval = mYPositions.length - mXOneOffset;
        // 使用System.arraycopy方式重新填充第一条波纹的数据
        // 第一个参数就是源数组,第二个参数是要复制的源数组中的起始位置,
        // 第三个参数是目标数组,第四个参数是要复制到的目标数组的起始位置,第五个参数是要复制的元素的长度
        System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0,
                yOneInterval);
        System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval,
                mXOneOffset);

        int yTwoInterval = mYPositions.length - mXTwoOffset;

        System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
                yTwoInterval);
        System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval,
                mXTwoOffset);

        int yThreeInterval = mYPositions.length - mXThreeOffset;

        System.arraycopy(mYPositions, mXThreeOffset, mResetThreeYPositions, 0,
                yThreeInterval);
        System.arraycopy(mYPositions, 0, mResetThreeYPositions, yThreeInterval,
                mXThreeOffset);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 记录下view的宽高
        mTotalWidth = w;
        mTotalHeight = h;
        // 用于保存原始波纹的y值
        mYPositions = new float[mTotalWidth];
        // 用于保存波纹一的y值
        mResetOneYPositions = new float[mTotalWidth];
        // 用于保存波纹二的y值
        mResetTwoYPositions = new float[mTotalWidth];
        // 用于保存波纹三的y值
        mResetThreeYPositions = new float[mTotalWidth];
        // 将周期定为view总宽度
        mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);

        // 根据view总宽度得出所有对应的y值
        for (int i = 0; i < mTotalWidth; i++) {
            mYPositions[i] = (float) (STRETCH_FACTOR_A
                    * Math.sin(mCycleFactorW * i) + OFFSET_Y);
        }
    }

    public void setHeight(int height) {
        this.height = height;
        postInvalidate();
    }

}

以上就是全部内容,有个bug,就是在转换的时候部分手机会出现卡顿,后面我加上插值器调整一下,先睡了。如果代码有什么不妥的地方欢迎指出。

源码地址

时间: 2024-10-18 23:06:37

Android属性动画与自定义View——实现vivo x6更新系统的动画效果的相关文章

Android自定义View——仿vivo i管家病毒扫描动画效果

技术是永无止境的,如果真的爱技术,那就勇敢的坚持下去.我很喜欢这句话,当我在遇到问题的时候.当我觉得代码枯燥的时候,我就会问自己,到底是不是真的热爱技术,这个时候,我心里总是起着波澜,我的答案是肯定的,我深深的爱着这门技术. 今天我们继续聊聊Android的自定义View系列.先看看效果吧: 这个是我手机杀毒软件的一个动画效果,类似于雷达搜索,所以用途还是很广泛的,特别是先了解一下这里的具体逻辑和写法,对技术的进步一定很有用. 先简单的分析一下这里的元素,主要有四个圆.一个扇形.还有八条虚线.当

[转]Android自定义控件三部曲系列完全解析(动画, 绘图, 自定义View)

来源:http://blog.csdn.net/harvic880925/article/details/50995268 一.自定义控件三部曲之动画篇 1.<自定义控件三部曲之动画篇(一)——alpha.scale.translate.rotate.set的xml属性及用法>2.<自定义控件三部曲之动画篇(二)——Interpolator插值器>3.<自定义控件三部曲之动画篇(三)—— 代码生成alpha.scale.translate.rotate.set及插值器动画&g

Android知识梳理之自定义View

虽然android本身给我们提供了形形色色的控件,基本能够满足日常开发的需求,但是面对日益同质化的app界面,和不同的业务需求.我们可能就需要自定义一些View来获得比较好的效果.自定义View是android开发者走向高级开发工程师必须要走的一关. 转载请标明出处:http://blog.csdn.net/unreliable_narrator/article/details/51274264 一,构造函数: 当我们创建一个类去继承View的时候,会要求我们至少去实现一个构造函数. publi

Android进阶之绘制-自定义View完全掌握(一)

Android的UI设计可以说是决定一个app质量的关键因素,因为人们在使用app的时候,最先映入眼帘的就是app的界面了,一个美观.充实的界面能够给用户带来非常好的体验,会在用户心中留下好的印象. 对于UI设计,Android原生的控件加上一些开源库的使用,已经能够满足大部分的UI需求,但是,某些比较新颖.花哨的控件效果,我们只能通过自定义View来实现,那么,从该篇博客开始,我将记录关于Android自定义View的学习内容,并将其整理呈现给大家. 我们来实现一个优酷菜单案例,在案例中会涉及

Android应用开发之自定义View触摸相关工具类全解

背景 最近有些乱,各种事情,各种交叉.好在还有一点上进心,于是继续将自定义这个系列的核心知识再梳理一下吧.关于自定义控件前面博文说过了,这里不会教你拿来主义,只授之以渔,如果你喜欢拿来主义,不好意思,请绕行,如果你喜欢得渔,那请继续. 前面我们已经叙述过了几篇关于自定义View涉及的东西,大家可以自己回过头去看我之前的博客,譬如事件处理.坐标系.工具类等.下面我们还是继续补充一些常用的自定义控件工具类. [工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请

Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)

转载请注明地址:http://blog.csdn.net/xiaanming/article/details/10298163 很多的时候,系统自带的View满足不了我们功能的需求,那么我们就需要自己来自定义一个能满足我们需求的View,自定义View我们需要先继承View,添加类的构造方法,重写父类View的一些方法,例如onDraw,为了我们自定义的View在一个项目中能够重用,有时候我们需要自定义其属性,举个很简单的例子,我在项目中的多个界面使用我自定义的View,每个界面该自定义View

【Android 应用开发】 自定义 View 组件 -- 圆形进度条

转载著名出处 : http://blog.csdn.net/shulianghan/article/details/40351487 代码下载 : -- CSDN 下载地址 : http://download.csdn.net/detail/han1202012/8069497 ; -- GitHub 地址 : https://github.com/han1202012/CircleProcess.git ; -- 工程示例 : 一. 相关知识点解析 1. 自定义 View 组件构造方法 构造方

Android进阶之绘制-自定义View完全掌握(三)

自定义View系列的第三篇博客,我们来学习如何实现自定义下拉框. 今天的程序,我们来实现这样的一个效果. 布局非常简单,我们直接开始编码. 修改activity_main.xml文件的代码. <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:

Android 如何 画 柱状图 -------自定义View

实现了 柱状图 根据 SeekBar的滑动 改变的效果: 图示效果: 自定义View的代码: package com.example.coustomviewdemo; import android.R.color; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.g