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

android实现icon动态旋转效果

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

北京

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

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

看不清楚,换张图片

免费获取短信验证码

android实现icon动态旋转效果

本文实例为大家分享了android实现icon动态旋转效果的具体代码,供大家参考,具体内容如下

碰到客户的这样一个需求,点击icon后,前景的icon开始旋转,背景的icon不动,就是这样一个效果

通过第三方的方法是不可能实现的,我这里是通过修改系统launcher的代码来实现。实现思路是在launcher中找到显示icon图标代码,并把这个图标覆盖掉。很多第手机的时钟icon是可以动态变化的,好在公司已经有人实现这个功能,可以借鉴

我这里先把时钟动态icon的实现说明下,需要的朋友可以参考。

写一个IconScript的基类继承Drawable

package com.android.launcher3;
 
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.util.Log;
 
public class IconScript extends Drawable{  
    public boolean isRuning = false;  
    public FastBitmapDrawable mFastBitmapDrawable = null;  
    protected Paint mPaint = new Paint();  
      
    public IconScript(){  
        mPaint.setAntiAlias(true);   
        mPaint.setFilterBitmap(true);  
    }  
      
    public void draw(Canvas canvas){  
        if(mFastBitmapDrawable != null){  
            Log.e("fly","IconScript=");
            canvas.drawBitmap(mFastBitmapDrawable.getBitmap(), null, getBounds(),mPaint);//画底图  
        }  
    }  
      
      
    public void run(View view){  
        isRuning = true;  
    }  
      
      
    public void onStop(){  
        isRuning = false;  
    }  
      
      
    public void onPause(){  
        isRuning = false;  
    }  
      
      
    public void onResume(){  
        isRuning = true;  
    }  
  
    @Override  
    public int getOpacity() {  
        // TODO Auto-generated method stub  
        return 0;  
    }  
  
    @Override  
    public void setAlpha(int arg0) {  
        // TODO Auto-generated method stub  
          
    }  
  
    @Override  
    public void setColorFilter(ColorFilter arg0) {  
        // TODO Auto-generated method stub  
          
    }  
      
    @Override  
    public int getIntrinsicWidth() {  
        int width = getBounds().width();  
        if (width == 0) {  
            width = mFastBitmapDrawable.getBitmap().getWidth();  
        }  
        return width;  
    }  
  
    @Override  
    public int getIntrinsicHeight() {  
        int height = getBounds().height();  
        if (height == 0) {  
            height = mFastBitmapDrawable.getBitmap().getHeight();  
        }  
        return height;  
    }  
  
    @Override  
    public int getMinimumWidth() {  
        return getBounds().width();  
    }  
  
    @Override  
    public int getMinimumHeight() {  
        return getBounds().height();  
    }  
      
    @Override  
    public void setFilterBitmap(boolean filterBitmap) {  
        mPaint.setFilterBitmap(filterBitmap);  
        mPaint.setAntiAlias(filterBitmap);  
    }  
      
    public void setFastBitmapDrawable(FastBitmapDrawable drawable){  
        mFastBitmapDrawable = drawable;  
    }  
      
    public FastBitmapDrawable setFastBitmapDrawable(){  
        return mFastBitmapDrawable;  
    }  
}  

核心类ClockScript继承IconScript

package com.android.launcher3;
 
 
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.format.Time;
import android.view.View;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
 
 
 
 
public class ClockScript extends IconScript {  
   Rect mRect = null;  
     
   private View mView;  
     
   private ClockThread mClockThread = null;  
     
   private boolean mIsShowInScreen = false;  
   Context mContext; 
 
 
   public ClockScript(Context context){  
       super();  
    mContext = context;  
   }  
   public void run(View view) {  
       mView = view;  
       mRect = getBounds();  
       if(mClockThread == null){  
           mClockThread = new ClockThread();  
           mClockThread.start();  
       }  
   }  
     
   @Override  
   public void onPause() {  
       mClockThread.pauseRun();  
       super.onPause();  
   }  
     
   @Override  
   public void onResume() {  
       mClockThread.resumeRun();  
       super.onResume();  
   }  
     
   @Override  
   public void onStop() {  
       mClockThread.stopRun();  
       super.onStop();  
   }  
     
 
   @Override  
   public void draw(Canvas canvas) {  
       super.draw(canvas);  
       mIsShowInScreen = true;  
 
 
       drawIndicator(canvas,mRect.centerX(),mRect.centerY(),mPaint);  
 
 
       if(mClockThread.wait){  
           mClockThread.resumeRun();  
       }  
   }  
     
 private void drawIndicator(Canvas canvas,int centerX,int centerY,Paint p){  
         
    Bitmap clockIcon = Utilities.createIconBitmap( BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher_clock),mContext);  
 
 
 
    int X = clockIcon.getWidth()/2;
    int Y = clockIcon.getHeight()/2;
    canvas.drawBitmap(clockIcon, null, getBounds(),p);//画底图  
 
    Time t=new Time();
     t.setToNow(); 
    p.setAntiAlias(true);
    p.setStrokeWidth(3);
    p.setColor(Color.WHITE);
    p.setStyle(Paint.Style.FILL);
 
 
//hour
canvas.drawLine(X, Y, int)(X + (clockIcon.getWidth()/2-35) * Math.cos((t.hour+(float)t.minute/60) * (Math.PI / 6) - Math.PI / 2)), (int)(Y + (clockIcon.getWidth()/2-35) * Math.sin((t.hour+(float)t.minute/60) * (Math.PI / 6) - Math.PI / 2)), p);
//minute
canvas.drawLine(X, Y,(int)(X + (clockIcon.getWidth()/2-27) * Math.cos(t.minute * (Math.PI / 30) - Math.PI / 2)),(int)(Y + (clockIcon.getWidth()/2-27) * Math.sin(t.minute * (Math.PI / 30) - Math.PI / 2)),p);
//second
p.setColor(Color.RED);
p.setStrokeWidth(1);
p.setStyle(Paint.Style.FILL);
canvas.drawLine(X, Y,(int)(X + (clockIcon.getWidth()/2-20) * Math.cos(t.second * (Math.PI / 30) - Math.PI / 2)),(int)(Y + (clockIcon.getWidth()/2-20) * Math.sin(t.second * (Math.PI / 30) - Math.PI / 2)),p);
      
p.setColor(Color.WHITE);
canvas.drawCircle(X, Y, 4, p);
p.setColor(Color.GRAY);
canvas.drawCircle(X, Y, 2, p);
 
}  
     
   class ClockThread extends Thread {  
       int times = 0;  
       boolean running = true;  
 
       public boolean wait = false;  
 
       public void stopRun() {  
           running = false;  
           synchronized (this) {  
               this.notify();  
           }  
       };  
 
       public void pauseRun() {  
           this.wait = true;  
           synchronized (this) {  
               this.notify();  
           }  
       }  
 
       public void resumeRun() {  
           this.wait = false;  
           synchronized (this) {  
               this.notify();  
           }  
       }  
 
       public void run() {  
           while (running) {  
               synchronized (mView) {  
                   mView.postInvalidate();  
               }  
                 
               if(!mIsShowInScreen){  
                   pauseRun();  
               }  
               mIsShowInScreen = false;  
               try {  
                   Thread.sleep(500);  
               } catch (Exception e) {  
                   System.out.println(e);  
               }  
                 
               synchronized (this) {  
                   if (wait) {  
                       try {  
                           wait();  
                       } catch (InterruptedException e) {  
                           // TODO Auto-generated catch block  
                           e.printStackTrace();  
                       }  
                   }  
               }  
           }  
       }  
   }  
 
}  

接下来就是如何初始化这个ClockScript,在Icon类里面添加代码

--- a/packages/apps/Launcher3/class="lazy" data-src/com/android/launcher3/IconCache.java
+++ b/packages/apps/Launcher3/class="lazy" data-src/com/android/launcher3/IconCache.java
@@ -84,6 +84,7 @@ public class IconCache {
         public CharSequence title = "";
         public CharSequence contentDescription = "";
         public boolean isLowResIcon;
+        public IconScript script;
     }
 
     private final HashMap<UserHandleCompat, Bitmap> mDefaultIcons = new HashMap<>();
@@ -591,7 +592,17 @@ public class IconCache {
        if (info != null) {
            entry.title = info.getLabel();
        }
-
+      
+       Log.e("IconCache ","componentName.getPackageName()="+componentName.getPackageName());
        //加了一个系统的属性来控制
+       if(null != entry && componentName.getPackageName().equals("com.android.deskclock")  && android.os.SystemProperties.getBoolean("launcher.calender.updateicon", false))
+       {        
+          Log.e("IconCache","clock init");
+           entry.script = new ClockScript(mContext);  
+       }
 
         return entry;
     }
 
@@ -891,4 +902,20 @@ public class IconCache {
             return null;
         }
     }
+    public IconScript getScript(Intent intent, UserHandleCompat user){  
+        synchronized (mCache) {  
+             ComponentName component = intent.getComponent();  
+            
+             if (component == null) {  
+                Log.e("IconCache ","component==null");
+                 return null;  
+             }  
+            LauncherActivityInfoCompat launcherActInfo = mLauncherApps.resolveActivity(intent, user);  
+            CacheEntry entry = cacheLocked(component, launcherActInfo,user, false, false);  
+            return entry.script;  
+        }  
+    }  

在BubbleTextView类中添加代码

--- a/packages/apps/Launcher3/class="lazy" data-src/com/android/launcher3/BubbleTextView.java
+++ b/packages/apps/Launcher3/class="lazy" data-src/com/android/launcher3/BubbleTextView.java
@@ -30,6 +30,7 @@ import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.KeyEvent;
@@ -38,6 +39,7 @@ import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewParent;
 import android.widget.TextView;
+import android.graphics.Rect;
 
 import com.android.launcher3.IconCache.IconLoadRequest;
 import com.android.launcher3.model.PackageItemInfo;
@@ -148,12 +150,44 @@ public class BubbleTextView extends TextView
 
     public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) {
         applyFromShortcutInfo(info, iconCache, false);
-    }
+        
+        mScript = info.getScript(iconCache); //zengxiao add
+        if(mScript!=null){
+        if(mScript!=null){
+        }else{
+               Log.e("rtyre","info.iconResource.packageName ------null");
+        }
+    }
+   private IconScript mScript;
+    @Override  
+    public void setCompoundDrawables(Drawable left, Drawable top,  
+            Drawable right, Drawable bottom) {  
+       
+        if(top != null){  
+             
+            if(mScript != null){  
+              
+                top = mScript;  
+                Rect rect=new Rect(0,0,LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize,LauncherAppState.getInstanc
+                mScript.setBounds(rect);  
+                         
+                if(!mScript.isRuning) 
+                {
+                            
+                               mScript.run(this);  
+                }
+            }  
+        }  
+      
+        super.setCompoundDrawables(left, top, right, bottom);  
+    }  
 
     public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
             boolean promiseStateChanged) {
         Bitmap b = info.getIcon(iconCache);
-
+        mScript = info.getScript(iconCache); 
         FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(b);
--- a/packages/apps/Launcher3/class="lazy" data-src/com/android/launcher3/ShortcutInfo.java
+++ b/packages/apps/Launcher3/class="lazy" data-src/com/android/launcher3/ShortcutInfo.java
@@ -304,5 +304,13 @@ public class ShortcutInfo extends ItemInfo {
     public boolean isDisabled() {
         return isDisabled != 0;
     }
+    
+    
+    public IconScript getScript(IconCache iconCache){  
+        return iconCache.getScript(promisedIntent != null ? promisedIntent : intent, user);  
+    }  
+    
 }

把这些代码添加上功能基本ok.

有了这个基础,我想要实现的效果就变得很简单,同样的定义一个WallpaperScript继承IconScript

package com.android.launcher3;
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.text.format.Time;
import android.view.View;
import android.util.Log;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.Paint;
import java.util.Calendar;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
 
public class WallpaperScript extends IconScript {  
    private float mDensity = 1.5f;  
    Time mTime = new Time();  
    int myCount =0;
    Context mContext; 
    Boolean isDraw =false;
       
    private View mView; 
       
    private boolean mIsShowInScreen = false;  
         
    private WallpaperThread mWallpaperThread = null;  
    
    int[] arr=new int[]{R.drawable.ic_launcher_wallpaper_1,R.drawable.ic_launcher_wallpaper_2,R.drawable.ic_launcher_wallpaper_3,
                        R.drawable.ic_launcher_wallpaper_4,R.drawable.ic_launcher_wallpaper_5,R.drawable.ic_launcher_wallpaper_6
    };
    int index = 0;
    public WallpaperScript(Context context) {  
        super();  
        mContext = context;  
    }
    public void run(View view) {  
       mView = view;  
       if(mWallpaperThread == null){
           //Log.d("WallpaperScript","mWallpaperThread  ");        
           mWallpaperThread = new WallpaperThread();  
           mWallpaperThread.start();  
       }
         IntentFilter filter = new IntentFilter();  
         filter.addAction("android.intent.action.WallpaperChange");            
             view.getContext().registerReceiver(new BroadcastReceiver() {  
              @Override  
              public void onReceive(Context arg0, Intent arg1) {  
                   Log.d("WallpaperScript","onReceive  ");    
                   isDraw = true;
                   mWallpaperThread.startRun();  
                   mWallpaperThread.start();
                  // myCount =0;
              }  
         }, filter);         
    }  
     
   @Override  
   public void onPause() {  
       mWallpaperThread.pauseRun();  
       super.onPause();  
   }  
     
   @Override  
   public void onResume() {  
       mWallpaperThread.resumeRun();  
       super.onResume();  
   }  
     
   @Override  
   public void onStop() {  
       mWallpaperThread.stopRun();  
       super.onStop();  
   }  
  
    @Override  
    public void draw(Canvas canvas) {  
        super.draw(canvas);  
        
        Bitmap wallpaperIconfirst = Utilities.createIconBitmap( BitmapFactory.decodeResource(mContext.getResources(), arr[0]),mContext);  
        
        canvas.drawBitmap(wallpaperIconfirst, null, getBounds(),mPaint);//默认显示的图片 
        if(isDraw){
            
            index = index%6;
            
            Bitmap wallpaperIcon = Utilities.createIconBitmap( BitmapFactory.decodeResource(mContext.getResources(), arr[index]),mContext);  
            
            index = index+1;
            
        
            myCount =myCount+1;
            
            canvas.drawBitmap(wallpaperIcon, null, getBounds(),mPaint);//画底图  
        
       
            //Log.d("WallpaperScript","WallpaperScript index "+index+" myCount "+myCount);
         
            if(myCount==49){
                myCount=0;
                mWallpaperThread.stopRun();
                //Log.d("WallpaperScript","WallpaperScript myCount " +myCount);
                isDraw = false;
            }    
            
        }
        
    }
      
    class WallpaperThread extends Thread {  
       int times = 0;  
       boolean running = true;  
 
       public boolean wait = false;  
       
        public void startRun() {  
           running = true;  
           synchronized (this) {  
               this.notify();  
           } 
       };  
 
       public void stopRun() {  
           running = false;  
           synchronized (this) {  
               this.notify();  
           } 
       };  
 
       public void pauseRun() {  
            
       }  
 
       public void resumeRun() {  
             
       }  
        
       public void run() { 
           Log.d("WallpaperScript","WallpaperThread running "+ running);        
           while (running) {  
               synchronized (mView) {
                   Log.d("WallpaperScript","WallpaperThread run()");                   
                   mView.postInvalidate();  
               }  
               try {  
                   Thread.sleep(50);  
               } catch (Exception e) {  
                   System.out.println(e);  
               }  
                 
            
           }  
       }  
   }  
  
}  

把所有的图片放到一个数组里面,然后轮流去绘制里面的图片,点击图标的时候会发送一个广播,通过广播去控制线程的开启,这样功能基本上实现。

另外,怎样去实现没有界面的app,这个只需要AndroidManifest设置。

android:theme="@android:style/Theme.NoDisplay"

切换锁屏壁纸和主屏幕壁纸的代码

WallpaperManager manager = WallpaperManager.getInstance(this);
try {
    manager.setBitmap(bitmap,null, true, WallpaperManager.FLAG_LOCK | WallpaperManager.FLAG_SYSTEM);
} catch (Exception e) {
    e.printStackTrace();
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程网。

免责声明:

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

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

android实现icon动态旋转效果

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

下载Word文档

猜你喜欢

Android 3D旋转动画效果实现分解

这篇文章主要介绍一下如何实现View的3D旋转效果,实现的主要原理就是围绕Y轴旋转,同时在Z轴方面上有一个深入的缩放。演示的demo主要有以下几个重点: 1,自定义旋转动画 2,动画做完后,重置ImageView 先看一下程序的运行效果:
2022-06-06

Android实现Bitmap位图旋转效果

位图的旋转也可以借助Matrix或者Canvas来实现。 通过postRotate方法设置旋转角度,然后用createBitmap方法创建一个经过旋转处理的Bitmap对象,最后用drawBitmap方法绘制到屏幕上,于是就实现了旋转操作。
2022-06-06

Android编程实现3D旋转效果实例

本文实例讲述了Android编程实现3D旋转效果的方法。分享给大家供大家参考,具体如下: 下面的示例是在Android中实现图片3D旋转的效果。 实现3D效果一般使用OpenGL,但在Android平台下可以不直接使用OpenGL,而是使用
2022-06-06

Android编程实现3D滑动旋转效果的方法

本文实例讲述了Android编程实现3D滑动旋转效果的方法。分享给大家供大家参考,具体如下: 这里我们通过代码实现一些滑动翻页的动画效果。 Animation实现动画有两个方式:帧动画(frame-by-frame animation)和补
2022-06-06

Android自定义View实现叶子飘动旋转效果(四)

上一篇实现了叶子飘动功能,《Android自定义叶子飘动》 现在实现旋转效果要实现这个效果,要在之前的功能上添加2个功能 1、通过matrix.postTranslate(int x, int y)在添加在Y轴上滑动 2、通过matrix.
2022-06-06

Android 实现旋转木马的音乐效果

一、百度在线音乐旋转木马效果就上面那个,当音乐在播放的时候,那个光碟轮子在转,就想旋转木马一般。感觉好好玩啊。 碰巧想起前阵子做音乐播放器,哎,那这个也可以做在手机的音乐播放器上,这样就代替了进度条了。 一想到,就兴奋,于是,首先画圆形,然
2022-06-06

CSS3怎么实现旋转圈动画效果

这篇文章主要讲解了“CSS3怎么实现旋转圈动画效果”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“CSS3怎么实现旋转圈动画效果”吧!效果:html代码:2023-07-04

css怎么实现旋转翻牌动画效果

小编给大家分享一下css怎么实现旋转翻牌动画效果,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!css动画之旋转翻牌效果,具体内容如下所示:1、我们先设置两个盒子大
2023-06-08

编程热搜

  • 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第一次实验

目录