AppWidget实现自定义view的另类实现

一、鸡汤

appwidget是android中小组件,我们经常说的widget其实是指的那些button、textview、imageview等这些小控件,而appwidget则是嵌入到别的app中的activity中显示的一种视图。通常我们的appwidget都是嵌入到luncher应用中的(我们经常说的桌面其实也是一款app也就是home luncher应用,手机里的应用会在其activity内显示一个启动图标),运行在luncher应用中,而其事件处理都是在本app内的进程中完成的,所以这里就会涉及到跨进程通信,而如果本应用想要跟appwidget的视图所运行的app通信,因为appwidget运行在别的进程中,只能使用remoteview去更新视图,remoteview相比于view具有跨进程的能力但是其支持的视图也是非常有线的,常用的大概就是textview、imageview、imagebutton、button、listview、gridview,其余的像自定义view、recycleview等等都是不支持的,所以appwidget的功能还是非常有限的。如果我们只是使用像textview这种没有子布局的控件那么使用方式是非常简单的,这里会涉及到:AppWidgetProvider、AppWidgetProviderInfo这两个类。而如果涉及到像listview、gridview这种,还会涉及到RemoteViewsService和RemoteViewsFactory。

AppWidgetProvider:该类继承自broadcastreceiver,需要在清单文件中注册<receiver>标签。

AppWidgetProviderInfo:该类只需写xml文件就可以了,xml放在res/xml下面,跟标签为<appwidget-provider>.

RemoteViewsService:该类继承自service,需要在清单文件中注册。

RemoteViewsFactory:该类为RemoteViewsService的内部类,处理service中的工作。

ok,有了上面的大体介绍,下面我们就这两种情况具体来介绍一下。

只含有textview、button等情况

首先我们要准备一个appwidget用来显示的布局文件,这里我们假定只有一个button,放在layout下面就ok,取名为widget_layout,具体代码就补贴出来了,你自己决定写什么样的效果,不过只能含有我上面说的哪几种情况哦。接下来我们需要准备一个AppWidgetProviderInfo类,上面知道这个类是在res/xml下面新建一个xml文件来自动生成的,具体代码如下类似:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_layout"
    android:minHeight="300dp"
    android:minResizeHeight="90dp"
    android:minResizeWidth="190dp"
    android:resizeMode="horizontal|vertical"
    android:minWidth="200dp">

    </appwidget-provider>

简要的介绍下属性值:

initialLayout:指定把appwidget放置在桌面上的时候的初始布局。

minHeight、minResizeHeight:前一个是指定appwidget的最小高度,后一个指定在允许重新测量appwidget高度的情况下的最少高度,后一个值必须要小于前一个值,否则其会被忽略。

resizeMode:允许在哪些方向上重新测量,我们都知道appwidget在桌面显示的时候会有横向和纵向上有多少个格子,比如2*2、4*4,系统就是根据其能提供的类型和minHeight的值算出一个新的ResizeHeight,如果这个值小于miniResizeHeight就会自动在对应方向上增加一个格子的大小。

其还有下面的属性:

android:configure:指定appwidget第一次放置在桌面上面的时候需要打开的activity,如果配置了这个属性下面将讲到的AppwidgetProvider中的onUpdate就不会在第一次被调用,以后的添加才执行改调用。

android:previewImage:指定appwidget在预览界面显示的图片,也就是我们选择appwidget的时候显示的界面,如果不设置改属性系统就会给app的icon图标作为预览。

android:widgetCategory="home_screen|searchbox|keyguard":用来设置appwidget可以在那些情况下显示,默认只能在home——screen上面显示。keyguard用来在锁屏的时候显示,searchbox实在搜索页面添加。

android:updatePeriodMillis:自动更新的时间,最少为半个小时。

ok,配置好上面的类之后我们来看看AppWidgetProvieder这个类,前面说了该类是继承自BroadcastReceiver,所以我们要在清单文件中配置<receiver>标签,如下所示:

  <receiver android:name=".WidgetProviderClass">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"></action>
                <action android:name="com.xinxue.action.TYPE_BTN"></action>
                <action android:name="com.xinxue.action.TYPE_LIST"></action>
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_provider"></meta-data>
        </receiver>

这里的<meta-data>是必须要写的,名字是固定的,resource就是我们创建的上面的那个xml文件的位置。其中

 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"></action>

这个是必须要写的一个action,用来接收appwidget更新的广播,后两个为我自己写的广播。下面我们来看看AppwidgetProvider中的代码:

public class WidgetProviderClass extends AppWidgetProvider {

}

其实我们只要继承它就可以了,里面的代码我们可以都不写,这样运行就会正常的使用appwidget了。前提是我们的widget的视图必须是没有包含子视图的布局,比如只有一个textview,去你的appwidget里面选择我们的小组件放在桌面,这里我贴一下运行的效果:

也许你的效果和我的不一样,那取决于你定义的appwidget的布局文件的效果。ok,简单的效果是有了,如果你想要给按钮添加上点击事件,我们只需要在appwidgetprovider的类中重写onUpdate方法,如下类似:

public class WidgetProviderClass extends AppWidgetProvider {
    public static final String BTNACTION = "com.xinxue.action.TYPE_BTN";

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
        //创建一个广播,点击按钮发送该广播
        Intent intent = new Intent(BTNACTION);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.widget_btn, pendingIntent);
        //如果你添加了多个实例的情况下需要下面的处理
        for (int i = 0; i < appWidgetIds.length; i++) {
            appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews);
        }
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        switch (intent.getAction()) {
            case BTNACTION:
                Toast.makeText(context, "点到我啦!", Toast.LENGTH_SHORT).show();
                break;

        }
        super.onReceive(context, intent);
    }
}

运行之后点击你的按钮就可以看到效果啦。我简单的给你介绍下上面的代码,在onUpdate方法中首先创建一个remoteview实例,再创建一个带有指定action的Intent,然后通过这个intent创建一个能发送广播的pendingIntent,然后调用remoteview的setOnClickPendingIntent方法绑定一个点击事件,点击按钮的时候会发送带有前面action的广播,要主要的是该方法的第一个参数为要点击的view的id。因为前面我们在清单文件中已经注册过这种类型的广播,在这里就可以收到该广播了,我们只需要重写其onReceiver方法,在里面过滤出我们需要的广播,在这里你就可以实现自己的点击逻辑了,比如打开app的首页,这里我简单的图个斯意思意思一下。效果图如下:

带有listview的情况

我们修改自己的布局文件,添加一个listview,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/kk"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

 <ListView
   android:id="@+id/widget_listview"
     android:background="#000"
     android:layout_width="match_parent"
     android:layout_height="match_parent">

 </ListView>

</LinearLayout>

OK,前面我们说到,要使用这种带有子布局的情况需要使用remoteviewsService和RemoteViewsFactory,那么我们新建一个类叫RemoteviewsServiceImp,让它继承与RemoteviewsService,然后实现里面的方法,不要忘记在manifest文件里面添加<service>标签,标签内的内容如下:

 <service
            android:name=".RemoteViewServiceImp"
            android:permission="android.permission.BIND_REMOTEVIEWS"></service>

**********注意权限哦!

因为remoteviewsservice的任务都是交给factory去完成的,这里我们就建立一个内部类让它实现remoteviewsfactory接口,然后重写里面的方法,完成后的代码如下:

package com.example.leixinxue.widgettest;

import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;

import java.util.ArrayList;

/**
 * Created by leixinxue on 16-8-8.
 */

public class RemoteViewServiceImp extends RemoteViewsService {

    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new RemoteviewFactoryImp();
    }

    class RemoteviewFactoryImp implements RemoteViewsFactory {
        @Override
        public void onCreate() {

        }

        @Override
        public void onDataSetChanged() {

        }

        @Override
        public void onDestroy() {

        }

        @Override
        public int getCount() {
            return 0;
        }

        @Override
        public RemoteViews getViewAt(int position) {
            return null;
        }

        @Override
        public RemoteViews getLoadingView() {
            return null;
        }

        @Override
        public int getViewTypeCount() {
            return 0;
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public boolean hasStableIds() {
            return false;
        }
    }
}

OK,下面我们就来写里面具体的代码。首先是让listview传到remoteviewsservice这里来,然后通过它传给remoteviewsfactory,在factory里面就是填充listview的布局内容,我们现在建立一个listview的item的布局文件,里面的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/item_textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginBottom="5dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="5dp"
        android:text="TextView"
        android:textColor="#fff"
        android:textSize="18sp" />
</LinearLayout>

下面我们来到APPwidgetprovider里面的onupdate方法里面修改代码,修改后的代码如下:

 @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);

//绑定service用来填充listview中的视图
        Intent intent = new Intent(context, RemoteViewServiceImp.class);
        remoteViews.setRemoteAdapter(R.id.widget_listview, intent);

        //如果你添加了多个实例的情况下需要下面的处理
        for (int i = 0; i < appWidgetIds.length; i++) {
            appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews);
        }
    }

我们来到factory里面绑定listview的视图,完成后的代码如下:

package com.example.leixinxue.widgettest;

import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;

import java.util.ArrayList;

/**
 * Created by leixinxue on 16-8-8.
 */

public class RemoteViewServiceImp extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new RemoteViewsFactoryImp(this, intent);
    }

    private static ArrayList<String> data;

    public static void loadData() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                data = HttpUtils.getData();
            }
        });
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    class RemoteViewsFactoryImp implements RemoteViewsFactory {
        private Intent requestIntent;
        private Context requestContext;

        public RemoteViewsFactoryImp(Context context, Intent intent) {
            requestContext = context;
            requestIntent = intent;
        }

        @Override
        public void onCreate() {
            loadData();
        }

        @Override
        public void onDataSetChanged() {

        }

        @Override
        public void onDestroy() {

        }

        @Override
        public int getCount() {
            return data.size();
        }

        @Override
        public RemoteViews getViewAt(int position) {
            RemoteViews remoteViews = new RemoteViews(requestContext.getPackageName(), R.layout.widget_item_layout);

            remoteViews.setTextViewText(R.id.item_textView, data.get(position));
            return remoteViews;
        }

        @Override
        public RemoteViews getLoadingView() {
            return null;
        }

        @Override
        public int getViewTypeCount() {
            return 1;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public boolean hasStableIds() {
            return false;
        }
    }
}

这里我使用了一个类来加载网上的数据,当然你可以用自己的方式去现实数据,这里可能有个方法大家不是很懂,就是那个tread.join()方法,它的作用是在线程执行完run方法之后再执行join后面的代码,我这里使用的目的是做有个同步,也就是在数据下载完成后再执行后面的代码。工具类这里就不贴代码了。

到这里我们就可以运行看下效果了。我的运行效果如下:

OK,下面我们给listview加上交互,给每一个item添加上点击事件,需要做需改provider和factory里面的代码,修改完成后的代码如下:

provider中的代码:

package com.example.leixinxue.widgettest;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import android.widget.Toast;

/**
 * Created by leixinxue on 16-8-8.
 */

public class WidgetProviderClass extends AppWidgetProvider {
    public static final String BTNACTION = "com.xinxue.action.TYPE_BTN";
    public static final String ITEMCLICK = "com.xinxue.action.TYPE_LIST";

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);

//绑定service用来填充listview中的视图
        Intent intent = new Intent(context, RemoteViewServiceImp.class);
        remoteViews.setRemoteAdapter(R.id.widget_listview, intent);
//添加item的点击事件
        Intent intent1 = new Intent(ITEMCLICK);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent1, PendingIntent.FLAG_CANCEL_CURRENT);
        remoteViews.setPendingIntentTemplate(R.id.widget_listview, pendingIntent);

        //如果你添加了多个实例的情况下需要下面的处理
        for (int i = 0; i < appWidgetIds.length; i++) {
            appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews);
        }
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        if (intent.getAction().equals(ITEMCLICK)) {
            Toast.makeText(context, intent.getIntExtra("position", 0) + "", Toast.LENGTH_SHORT).show();
        }
    }
}

factory中的代码:

package com.example.leixinxue.widgettest;

import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;

import java.util.ArrayList;

/**
 * Created by leixinxue on 16-8-8.
 */

public class RemoteViewServiceImp extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new RemoteViewsFactoryImp(this, intent);
    }

    private static ArrayList<String> data;

    public static void loadData() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                data = HttpUtils.getData();
            }
        });
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    class RemoteViewsFactoryImp implements RemoteViewsFactory {
        private Intent requestIntent;
        private Context requestContext;

        public RemoteViewsFactoryImp(Context context, Intent intent) {
            requestContext = context;
            requestIntent = intent;
        }

        @Override
        public void onCreate() {
            loadData();
        }

        @Override
        public void onDataSetChanged() {

        }

        @Override
        public void onDestroy() {

        }

        @Override
        public int getCount() {
            return data.size();
        }

        @Override
        public RemoteViews getViewAt(int position) {
            RemoteViews remoteViews = new RemoteViews(requestContext.getPackageName(), R.layout.widget_item_layout);
            //listview的点击事件
            Intent intent = new Intent(WidgetProviderClass.ITEMCLICK);
            intent.putExtra("position", position);
            remoteViews.setOnClickFillInIntent(R.id.item_textView, intent);

            remoteViews.setTextViewText(R.id.item_textView, data.get(position));
            return remoteViews;
        }

        @Override
        public RemoteViews getLoadingView() {
            return null;
        }

        @Override
        public int getViewTypeCount() {
            return 1;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public boolean hasStableIds() {
            return false;
        }
    }
}

我们运行看下效果,OK,完美。到这里你可能已经学完了APPwidget的教程了,看Google的官方API你可能学到的也就只有这些。

全球首创让你的APPwidget实现自定义view

博客地址:blog.csdn.net/qq379454816------>欢乐斗佛的博客

我们都知道,产品经理大部分是SB,他们不知道怎么去编程,不知道一个模块的功能怎么去实现,可是他们为了酷炫,就是设计出一些很难实现的效果图,如果它设计一个很难得效果图,而这个效果图无法使用基本的视图去实现,必须要用自定义的方式去实现,而APPwidget又不支持自定义view,难道我们用修改底层framework的方式去实现?这当然是一种解决问题的办法,但是这个通常是无法实现的,于是乎你就百度一下,高级一点的屌丝程序员可能Google一下,结果很让人懵逼啊,能搜到几个条目告诉你怎么用自定义view去实现,可是打开一看~~~~~MD,修改framework,额,再次懵逼~~~。绝境之中遇贵人这种桥段是电视惯用的伎俩,但也不是现实生活中没有这种情况,下面我就教你一种全宇宙独创的方式去实现一种自定义view来实现APPwidget无法实现自定义view的窘境,这种方式简单的一B啊,可是如果我不告诉你,你却一辈子也无法去实现,而一旦思路打开,你的奇思妙想就会如尿崩一发而不可收拾,那就是自定义图片!!!什么?这~~~~~。

我们知道APPwidget是可以使用imageview的,而remoteview中有一个方法可以实现替换imageview中的图片:remoteViews.setImageViewBitmap(int viewid,Bitmap bitmap);两个参数,第一个为我们的imageview的id,第二个就是一个图片,imageview我们不可以动手脚,可是这个bitmap的来源我们就可以自己去把控了,你可以使用一个图片利用bitmapfactory来转换,可以使用xml文件来定义一个图片,最大自由度的使用方式是自定义一个bitmap,然后在这个bitmap上面实现我们的复杂效果。考虑到这篇文章前前后后的写了快一个月了,中间还有好多效果图没有给出,而工作忙的我实在没有大片的时间去完善这篇文章,不过中间的步骤什么的我是描述清楚了,上面两种方式的实现百度上面也有相应的文章,这里就部打算去完善了,重点给这个自定义的东西写一下,大家如果对前面的东西还有不明白的地方可以给我留言或者私信。

ok,废话说了一大篇,该是进入正题了。要实现一个自定义的bitmap,我们首先想到是继承 bitmap(有想到继承view的可以在下面排个队,到我这里领赏),可是当继承它的时候你会发现报错了,鼠标一上去一看~~额,bitmap是final类型的,你妹哦!android中图片还有一种方式就是drawable,该方法可以正常使用,不过其尺寸什么的不好去控制,转换bitmap的时候也好麻烦,这里我们就不用该方法了,我们知道自定义都离不开canvas,而构建一个canvas的时候其构造方法中可以传入一个bitmap,那我们何不就此入手实现我们的效果图!这里我说下思路,canvas就好比一个画布,而bitmap就好比一个布,我们给画布上面的布替换成bitmap这块布,然后所有的东西都画到这个布上面,这样bitmap就有内容了,再拿到这个bitmap我们就可以实现我们的目的了。大概就是这样一个过程,我先贴上我在乐视管家项目中的效果图:

原文地址:http://blog.csdn.net/qq379454816/article/details/52161495

时间: 2024-08-06 15:59:40

AppWidget实现自定义view的另类实现的相关文章

自定义View 篇三 《手动打造ViewPage》

有了之前自定义View的理论基础,有了ViewPage.事件分发机制.滑动冲突.Scroller使用等相关知识的铺垫,今天纯手动打造一款ViewPage. 1.完成基本的显示: 在MainActivity中: public class MainActivity extends AppCompatActivity { private MyViewPage mViewPage; int[] imageIds = new int[]{ R.drawable.pic_0, R.drawable.pic_

九点(九宫格)式手势解锁自定义view

周末闲着没事,写了个手势解锁的view,实现起来也蛮快的,半天多一点时间就完事.把源码和资源贴出来,给大家分享,希望对大家有用. 效果,就跟手机上的九点手势解锁一样,上个图吧: 过程嘛感觉确实没啥好讲的了,涉及的知识以前的博客都说过了,无非就是canva,paint,touch事件这些,画画圆圈画画线条,剩下的就是细节处理逻辑了.都在代码里,所以这里就主要是贴资源吧. 这个自定义view就一个类,源码如下: package com.cc.library.view; import android.

iOS开发——笔记篇&amp;关于字典plist读取/字典转模型/自定义View/MVC/Xib的使用/MJExtension使用总结

关于字典plist读取/字典转模型/自定义View/MVC/Xib的使用/MJExtension使用总结 一:Plist读取 1 /******************************************************************************/ 2 一:简单plist读取 3 4 1:定义一个数组用来保存读取出来的plist数据 5 @property (nonatomic, strong) NSArray *shops; 6 7 2:使用懒加载的方

自定义View之实现日出日落太阳动效

以前也很羡慕网上大神随手写写就是一个很漂亮的自定义控件,所以我下决心也要学着去写,刚好最近复习了Android View的绘制流程知识,看来看去就是那些个知识点,没点产出总感觉很迷.现在个人呢用的是华为荣耀8手机,碰巧在看自带的天气APP时,滑到最下面看到那个动效图:日出时间和日落时间上边是一个半圆,白天任意的时刻(在日出和日落时间之间)都有对应一个太阳从日出时刻沿着半圆弧做动画特效,个人第一感觉就是:就拿这个来练练手啦!于是拿着笔和纸,画了模型图,甚至求什么sin.cos函数,有点过分了哈,还

Android 自定义View

最近在看鸿洋大神的博客,在看到自定义部分View部分时,想到之前案子中经常会要用到"图片 + 文字"这类控件,如下图所示: 之前的做法是在布局文件中,将一个Image & TextView组件放在LinearLayout/RelativeLayout中.今天就尝试了通过自定义View的方式来实现"图片 + 文字"组件. 首先在/res/value/目录下新建attrs.xml文件,在该文件中为CustomTextView定义以下几个attr.分别为 1.文字

Android自定义View探索(一)—生命周期

Activity代码: public class FiveActivity extends AppCompatActivity { private MyView myView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e("log", "Activity生命周期:onCreate"); setConte

Android自定义view之仿支付宝芝麻信用仪表盘

自定义view练习 仿支付宝芝麻信用的仪表盘 对比图: 首先是自定义一些属性,可自己再添加,挺基础的,上代码 <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="RoundIndicatorView"> <!--最大数值--> <attr name="maxNum" form

自定义view—折线图

学习导航 第一节:http://blog.csdn.net/bobo8945510/article/details/53197727 -自定义View-自定义属性及引用 第二节:http://blog.csdn.net/bobo8945510/article/details/53203233 自定义view02-图形绘制 第三节:http://blog.csdn.net/bobo8945510/article/details/53213938 自定义View-绘图基础之Path 第四节:http

Android笔记自定义View之制作表盘界面

前言 最近我跟自定义View杠上了,甚至说有点上瘾到走火入魔了.身为菜鸟的我自然要查阅大量的资料,学习大神们的代码,这不,前两天正好在郭神在微信公众号里推送一片自定义控件的文章--一步步实现精美的钟表界面.正适合我这种菜鸟来学习,闲着没事,我就差不多依葫芦画瓢也写了一个自定义表盘View,现在纯粹最为笔记记录下来.先展示下效果图: 下面进入正题 自定义表盘属性 老规矩,先在attrs文件里添加表盘自定义属性 <declare-styleable name="WatchView"&