我的编程空间,编程开发者的网络收藏夹
学习永远不晚

SurfaceView开发[捉小猪]手机游戏 (一)

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

SurfaceView开发[捉小猪]手机游戏 (一)

先上效果图:

这里写图片描述这里写图片描述 

哈哈, 说下实现思路:
我们可以把每一个树桩, 小猪, 车厢都看成是一个Drawable, 这个Drawable里面保存了x, y坐标, 我们的SurfaceView在draw的时候, 就把这些Drawable draw出来.

那可能有的小伙伴就会问了:
1. 那小猪是怎么让它跑起来, 并且腿部还不断地在动呢?
2. 还有小猪是怎么找到出路的呢?

刚刚我们讲过小猪是Drawable, 其实我们自定义的这个Drawable就是一个帧动画, 它里面有一个Bitmap数组, 一个currentIndex(这个用来记录当前帧), 我们在子线程里面不断更新这个currentIndex, 当Drawable被调用draw的时候, 就根据currentIndex来从Bitmap数组里面取对应的bitmap出来. 刚刚还讲过Drawable里面保存了当前x, y坐标, 我们的路径动画在播放的时候, 就不断的更新里面的坐标, 另外, SurfaceView那边也不断的调用这些Drawable的draw方法, 把他们画出来, 这样小猪就可以边移动, 边播放奔跑的动画了, 哈哈.

小猪找出路的话, 我们先看看这个:

这里写图片描述 

哈哈哈, 这样思路是不是清晰了好多.
其实我们的SurfaceView里面有一个Rect二维数组, 用来存放这些矩形, 小猪离开手指之后, 就开始从小猪当前所在的矩形,
用广度优先遍历, 找到一条最短的路径(比如: [5,5 5,4 5,3 5,2 5,1 5,0]这样的), 然后再根据这条路径在Rect数组中找到对应的矩形, 最后根据这些对应的矩形的坐标来确定出Path.
哈哈, 有了Path小猪就可以跑了.

下面我们先来看看那个自定义的Drawable怎么写 (下面的那个ThreadPool类就是我们自己封装的一个单例的线程池):



public class MyDrawable extends Drawable implements Cloneable {

    private final int mDelay;//帧延时
    private final byte[] mLock;//控制线程暂停的锁
    private Semaphore mSemaphore;//来用控制线程更新问题
    private Bitmap[] mBitmaps;//帧
    private Paint mPaint;
    private int mCurrentIndex;//当前帧索引
    private float x, y;//当前坐标
    private Future mTask;//帧动画播放的任务
    private volatile boolean isPaused;//已暂停

    public MyDrawable(int delay, Bitmap... bitmaps) {
        mSemaphore = new Semaphore(1);
        mBitmaps = bitmaps;
        mDelay = delay;
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mLock = new byte[0];
    }

    public void start() {
        stop();
        mTask = ThreadPool.getInstance().execute(() -> {
            while (true) {
                synchronized (mLock) {
                    while (isPaused) {
                        try {
                            mLock.wait();
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                }
                try {
                    Thread.sleep(mDelay);
                } catch (InterruptedException e) {
                    return;
                }
                try {
                    mSemaphore.acquire();
                } catch (InterruptedException e) {
                    return;
                }
                mCurrentIndex++;
                if (mCurrentIndex == mBitmaps.length) {
                    mCurrentIndex = 0;
                }
                mSemaphore.release();
            }
        });
    }

    void pause() {
        isPaused = true;
    }

    void resume() {
        isPaused = false;
        synchronized (mLock) {
            mLock.notifyAll();
        }
    }

    private void stop() {
        if (mTask != null) {
            mTask.cancel(true);
            mTask = null;
            mCurrentIndex = 0;
        }
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        try {
            mSemaphore.acquire();
        } catch (InterruptedException e) {
            return;
        }
        canvas.drawBitmap(mBitmaps[mCurrentIndex], x, y, mPaint);
        mSemaphore.release();
    }

    public void release() {
        stop();
        if (mBitmaps != null) {
            for (Bitmap bitmap : mBitmaps) {
                if (bitmap != null && !bitmap.isRecycled()) {
                    bitmap.recycle();
                }
            }
        }
        mBitmaps = null;
        mPaint = null;
        mTask = null;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }

    public Bitmap getBitmap() {
        Bitmap result = null;
        if (mBitmaps != null && mBitmaps.length > 0) {
            result = mBitmaps[0];
        }
        return result;
    }

    @Override
    public int getIntrinsicWidth() {
        if (mBitmaps.length == 0) {
            return 0;
        }
        return mBitmaps[0].getWidth();
    }

    @Override
    public int getIntrinsicHeight() {
        if (mBitmaps.length == 0) {
            return 0;
        }
        return mBitmaps[0].getHeight();
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    @SuppressWarnings("MethodDoesntCallSuperMethod")
    public MyDrawable clone() {
        return new MyDrawable(0, mBitmaps[0]);
    }
}

start方法大概就是开启一个子线程, 每次指定延时过后就更新currentIndex, currentIndex超出范围就置0, 这样就可以一直循环播放下去了, 哈哈.
mSemaphore是当执行draw的时候, 用来锁定currentIndex不让更新的.

好了, 现在有了Drawable, 我们再来看看Path是怎么播放的:
我们可以先获取到Path上面的点, 有了这些点接下来就非常简单了.
获取Path上面的点坐标的方法大家应该也很熟悉了吧, 代码就不贴出来了,5.0及以上系统用Path的approximate方法, 5.0系统以下的用PathMeasure类. 具体代码在SDK里面也可以找到.
播放Path的话, 我们可以自定义一个PathAnimation:
其实我们自定义的这个PathAnimation播放Path的逻辑也非常简单:当start方法执行的时候,记录一下开始时间,然后一个while循环,条件就是: 当前时间 - 开始时间 < 动画时长, 然后根据当前动画已经播放的时长和总动画时长计算出当前动画的播放进度, 然后我们就可以用这个progress来获取Path上对应的点,看看完整的代码:


public class PathAnimation {

    private Keyframes mPathKeyframes;//关键帧
    private long mAnimationDuration;//动画时长
    private OnAnimationUpdateListener mOnAnimationUpdateListener;//动画更新监听
    private AnimationListener mAnimationListener;//动画事件监听
    private volatile boolean isAnimationRepeat, //反复播放的动画
            isAnimationStopped,//已停止
            isAnimationCanceled, //已取消 (停止和取消的区别: 取消是在动画播放完之前主动取消的,  停止是动画播放完,自动停止的)
            isAnimationEndListenerCalled;//动画已取消的监听已经回调

    PathAnimation(MyPath path) {
        updatePath(path);
    }

    public PathAnimation setDuration(long duration) {
        mAnimationDuration = duration;
        return this;
    }

    void updatePath(MyPath path) {
        //根据系统版本选择更合适的关键帧类
        mPathKeyframes = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? new PathKeyframes(path) : new PathKeyframesSupport(path);
    }

    OnAnimationUpdateListener getUpdateListener() {
        return mOnAnimationUpdateListener;
    }

    void setUpdateListener(OnAnimationUpdateListener listener) {
        mOnAnimationUpdateListener = listener;
    }

    
    public PathAnimation setRepeat(boolean isAnimationRepeat) {
        this.isAnimationRepeat = isAnimationRepeat;
        return this;
    }

    boolean isAnimationRepeat() {
        return isAnimationRepeat;
    }

    AnimationListener getAnimationListener() {
        return mAnimationListener;
    }

    void setAnimationListener(AnimationListener listener) {
        mAnimationListener = listener;
    }

    void start() {
        if (mAnimationDuration > 0) {
            ThreadPool.getInstance().execute(() -> {
                isAnimationStopped = false;
                isAnimationCanceled = false;
                isAnimationEndListenerCalled = false;
                final long startTime = SystemClock.uptimeMillis();
                long currentPlayedDuration;//当前动画已经播放的时长
                if (mAnimationListener != null) {
                    mAnimationListener.onAnimationStart();
                }
                while ((currentPlayedDuration = SystemClock.uptimeMillis() - startTime) < mAnimationDuration) {
                    //如果动画被打断则跳出循环
                    if (isAnimationInterrupted()) {
                        break;
                    }
                    //根据当前动画已经播放的时长和总动画时长计算出当前动画的播放进度
                    float progress = (float) currentPlayedDuration / (float) mAnimationDuration;
                    if (mOnAnimationUpdateListener != null) {
                        if (!isAnimationInterrupted()) {
                            mOnAnimationUpdateListener.onUpdate(progress, mPathKeyframes.getValue(progress));
                        }
                    }
                }
                if (isAnimationRepeat && !isAnimationInterrupted()) {
                    //如果是设置了重复并且还没有被取消,则重复播放动画
                    mPathKeyframes.reverse();
                    if (mAnimationListener != null) {
                        mAnimationListener.onAnimationRepeat();
                    }
                    start();
                } else {
                    isAnimationStopped = true;
                    if (mAnimationListener != null) {
                        //判断应该回调哪一个接口
                        if (isAnimationCanceled) {
                            mAnimationListener.onAnimationCanceled();
                        } else {
                            mAnimationListener.onAnimationEnd();
                        }
                    }
                    //标记接口已回调
                    isAnimationEndListenerCalled = true;
                }
            });
        }
    }

    
    private void waitStopped() {
        isAnimationStopped = true;
        //noinspection StatementWithEmptyBody
        while (!isAnimationEndListenerCalled) {
        }
    }

    
    private void waitCancel(){
        isAnimationCanceled = true;
        //noinspection StatementWithEmptyBody
        while (!isAnimationEndListenerCalled) {
        }
    }

    void stop() {
        waitStopped();
    }

    void cancel() {
        waitCancel();
    }

    
    private boolean isAnimationInterrupted() {
        return isAnimationCanceled || isAnimationStopped;
    }

    public interface OnAnimationUpdateListener {
        void onUpdate(float currentProgress, PointF position);
    }

    public interface AnimationListener {
        void onAnimationStart();//动画开始

        void onAnimationEnd();//动画结束

        void onAnimationCanceled();//动画取消

        void onAnimationRepeat();//动画重复播放
    }
}

我们通过setUpdateListener方法来监听动画进度, OnAnimationUpdateListener接口的onUpdate方法参数还有一个PointF, 这个PointF就是根据动画当前进度从mPathKeyframes中获取到Path所对应的坐标点.

我们来写一个demo来看看这个PathAnimation的效果:
一
哈哈, 可以了, 是我们想要的效果.

现在动画什么的都准备好了,就差怎么把出路变成Path了,我们先来看看怎么找出路:
上面说到,屏幕上都铺满了矩形,我们可以再创建一个int类型的二维数组,用来保存这些矩形的状态(空闲:0,小猪占用:1,树桩占用:2)
我们把这个数组打印出来是这样的:


    0   0   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0

我们再看看这个数组:(空闲:0,小猪占用:1,树桩占用:2)


    0   0   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0   0
    0   0   0   0   2   2   0   0   0
      0   0   0   2   1   2   0   0   0
    0   0   0   2   0   2   0   0   0
      2   0   2   0   2   0   0   0   0
    2   0   0   0   0   0   0   0   0
      0   2   2   2   2   0   0   0   0
    0   2   0   0   0   0   0   0   0

这时候就要用到一个 “广度优先遍历” ,思路就是 (逻辑有点复杂,一次看不懂看多几次就明白了):


    先传入这个状态数组和当前小猪的坐标;
    创建一个List<List<Point>>,存放出路,名字就叫做footprints吧;
    创建一个队列,这个队列存放待查找的点;
    当前小猪的坐标先放进footprints;
    当前小猪的坐标入队;
    标记小猪坐标已经走过;
    进入循环 (循环条件就是队列不为空){
        创建一个临时的footprints;(因为最多可能有6个新的路径)
        队头出队;
        寻找周围6个方向(上,下,左,右,左上,右上,左下,右下)可以到达的位置 (不包括越界的、标记过的、不是空闲的);
        遍历这个可到达位置的数组{
            遍历footprints{
                检查队头的坐标是否跟footprints的元素(List<Point>)的最后一个元素(Point)的位置(x,y)是一样的(即可以链接)
                (比如: 现在footprints是[[(5,5), (5,4)], [(6,5), (6,4)]],队头的坐标是(5,4), 那可达位置的数组就可能是[(5,3), (5,5), (4,4), (4,5), (6,4), (6,5)]){
                    则创建一个新的List<Point>;
                    add footprints的元素(比如: [(5,5), (5,4)]);
                    再add可达位置的坐标(比如: (5,3);
                    临时的footprints add 这个新的List (那临时的footprints现在就是 [[(5,5), (5,4), (5,3)]]了);
                }                 
            }
            检查本次可达位置的坐标是否已经是在边界 (已经找到出路){
                footprints add 临时的footprints的元素;
                遍历footprints{
                    判断footprints的元素的最后一位是否边界位置{
                        return 这个footprints的元素; (必然是最短的路径);
                    }
                }
            }
            队列入队本次可达位置的坐标;
        }
        (本次没有找到出路) footprints addAll 临时的footprints的元素,准备下一轮循环;
    }
    执行到了这里, 即表示没有出路, 如果footprints里面是空的话,我们直接返回null,如果不为空,就返回footprints最后一个元素,即能走的最长的一条路径;

好了,我们看看代码是怎么写的 (WayData等同于Point, 里面也保存有x, y坐标点):


    public static List<WayData> findWay(int[][] items, WayData currentPos) {
        //获取数组的尺寸
        int verticalCount = items.length;
        int horizontalCount = items[0].length;
        //创建队列
        Queue<WayData> way = new ArrayDeque<>();
        //出路
        List<List<WayData>> footprints = new ArrayList<>();
        //复制一个新的数组 (因为要标记状态)
        int[][] pattern = new int[verticalCount][horizontalCount];
        for (int vertical = 0; vertical < verticalCount; vertical++) {
            System.arraycopy(items[vertical], 0, pattern[vertical], 0, horizontalCount);
        }
        //当前坐标入队
        way.offer(currentPos);
        //添加进集合
        List<WayData> temp = new ArrayList<>();
        temp.add(currentPos);
        footprints.add(temp);
        //标记状态 (已走过)
        pattern[currentPos.y][currentPos.x] = STATE_WALKED;

        while (!way.isEmpty()) {
            //队头出队
            WayData header = way.poll();
            //以header为中心,获取周围可以到达的点(即未被占用,未标记过的)(这个方法在获取到可到达的点时,会标记这个点为: 已走过)
            List<WayData> directions = getCanArrivePos(pattern, header);
            //创建临时的footprints
            List<List<WayData>> footprintsTemp = new ArrayList<>();
            //遍历可到达的点
            for (int i = 0; i < directions.size(); i++) {
                WayData direction = directions.get(i);

                for (List<WayData> tmp : footprints) {
                    //检查是否可以链接
                    if (canLinks(header, tmp)) {
                        List<WayData> list = new ArrayList<>();
                        list.addAll(tmp);
                        list.add(direction);
                        footprintsTemp.add(list);
                    }
                }
                //检查是否已达到边界
                if (isEdge(verticalCount, horizontalCount, direction)) {
                    if (!footprintsTemp.isEmpty()) {
                        footprints.addAll(footprintsTemp);
                    }
                    //返回最短的出路
                    for (List<WayData> tmp : footprints) {
                        if (!tmp.isEmpty() && isEdge2(verticalCount, horizontalCount, tmp)) {
                            return tmp;
                        }
                    }
                }
                //本次未找到出路,入队这个可到达的点
                way.offer(direction);

            }
            //准备下一轮循环
            if (!footprintsTemp.isEmpty()) {
                footprints.addAll(footprintsTemp);
            }
        }
        //没有出路,返回能走的最长的一条路径;
        return footprints.isEmpty() ? null : footprints.get(footprints.size() - 1);
    }

getCanArrivePos方法:


    
    public static List<WayData> getCanArrivePos(int[][] items, WayData currentPos) {
        int verticalCount = items.length;
        int horizontalCount = items[0].length;
        List<WayData> result = new ArrayList<>();
        int offset = currentPos.y % 2 == 0 ? 0 : 1, offset2 = currentPos.y % 2 == 0 ? 1 : 0;
        for (int i = 0; i < 6; i++) {
            WayData tmp = getNextPosition(currentPos, offset, offset2, i);
            if ((tmp.x > -1 && tmp.x < horizontalCount) && (tmp.y > -1 && tmp.y < verticalCount)) {
                if (items[tmp.y][tmp.x] != Item.STATE_SELECTED && items[tmp.y][tmp.x] != Item.STATE_OCCUPIED && items[tmp.y][tmp.x] != STATE_WALKED) {
                    result.add(tmp);
                    items[tmp.y][tmp.x] = STATE_WALKED;
                }
            }
        }
        //打乱它,为了让方向顺序不一样, 即每次都不同
        Collections.shuffle(result);
        return result;
    } 

getNextPosition方法:


    
    private static WayData getNextPosition(WayData currentPos, int offset, int offset2, int direction) {
        WayData result = new WayData(currentPos.x, currentPos.y);
        switch (direction) {
            case 0:
                //左
                result.x -= 1;
                break;
            case 1:
                //左上
                result.x -= offset;
                result.y -= 1;
                break;
            case 2:
                //左下
                result.x -= offset;
                result.y += 1;
                break;
            case 3:
                //右
                result.x += 1;
                break;
            case 4:
                //右上
                result.x += offset2;
                result.y -= 1;
                break;
            case 5:
                //右下
                result.x += offset2;
                result.y += 1;
                break;
        }
        return result;
    }

我们执行findWay方法,就会得到这个结果:


    0   0   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0   0
    0   0   0   0   2   2   0   0   0
      0   0   0   2   1   2   0   0   0
    0   0   0   2   *   2   0   0   0
      2   0   2   *   2   0   0   0   0
    2   *   *   *   0   0   0   0   0
      *   2   2   2   2   0   0   0   0
    0   2   0   0   0   0   0   0   0

哈哈,是不是很好玩, 我们将这条出路的坐标,分别获取到对应的Rect,再根据这个Rect的坐标来连接成Path, 然后我们的小猪就可以跑啦.

本文到此结束,有错误的地方请指出,谢谢大家!

SurfaceView开发[捉小猪]手机游戏 (二)

完整代码地址: https://github.com/wuyr/CatchPiggy

游戏主页: https://wuyr.github.io/

到此这篇关于SurfaceView开发[捉小猪]手机游戏 (一)的文章就介绍到这了,更多相关SurfaceView游戏内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

SurfaceView开发[捉小猪]手机游戏 (一)

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

微信跳一跳小程序游戏如何开发

本篇内容主要讲解“微信跳一跳小程序游戏如何开发”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“微信跳一跳小程序游戏如何开发”吧!  小游戏只有以下两个必要文件:  game.js 小游戏主程序入口
2023-06-26

手机游戏开发需要具备哪些技术

本文概述了手机游戏开发所需的技术,包括核心技术(编程语言、游戏引擎、物理引擎)、基础技术(数据结构、算法、图形学)和高级技术(人工智能、云技术)。此外,还介绍了专业化技术(2D/3D图形、音频处理、用户界面设计、游戏分析、质量保证)和持续学习的重要性。
手机游戏开发需要具备哪些技术
2024-04-02

怎么使用Vue开发一个五子棋小游戏

这篇文章主要讲解了“怎么使用Vue开发一个五子棋小游戏”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么使用Vue开发一个五子棋小游戏”吧!1.绘制游戏区域和游戏元素开始写代码之前,一定要记
2023-07-02

探索阿里云手机游戏服务器为游戏开发提供无限可能

随着科技的发展,手机游戏的市场规模正在不断扩大,吸引了越来越多的玩家。作为开发者,如何为用户提供高质量的游戏体验呢?阿里云手机游戏服务器正是为此提供了解决方案。本文将详细介绍阿里云手机游戏服务器,让你了解它的功能、优势以及使用方法。阿里云手机游戏服务器是一种基于云计算技术的游戏服务器解决方案,专为手机游戏设计,可
探索阿里云手机游戏服务器为游戏开发提供无限可能
2023-12-16

【小程序】快来开发你的第一个微信小游戏(详细流程)

🥳 作者:伯子南 😎 坚信: 好记性不如乱笔头,独乐乐不如众乐乐 💪 个人主页:https://blog.csdn.net/qq_34577234?spm=1010.2135.3001.5
2023-08-16

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录