关于MediaController的自定义

2024-03-18 09:48
文章标签 自定义 mediacontroller

本文主要是介绍关于MediaController的自定义,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一篇博客介绍了videoview和自定义该类,这篇想介绍一下MediaController,本人接触android也就几个月,其中还有一些问题,希望和大家一起进步。

自定义其实就是将android的MediaController源码摘出来,然后对其进行改编定义成自己的类。

摘出来和修改如下:

import java.util.Formatter;
import java.util.Locale;import com.android.internal.policy.PolicyManager;import android.content.Context;
import android.graphics.PixelFormat;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
//import android.view.View.OnLayoutChangeListener;
//import android.view.View.OnTouchListener;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
//import android.widget.FrameLayout.LayoutParams;
//import android.widget.MediaController.MediaPlayerControl;
import android.widget.SeekBar.OnSeekBarChangeListener;public class MediaController extends FrameLayout {private MediaPlayerControl  mPlayer;private Context             mContext;private View                mAnchor;private View                mRoot;private WindowManager       mWindowManager;private Window              mWindow;private View                mDecor;private WindowManager.LayoutParams mDecorLayoutParams;private ProgressBar         mProgress;private TextView            mEndTime, mCurrentTime;private boolean             mShowing;private boolean             mDragging;private static final int    sDefaultTimeout = 3000;private static final int    FADE_OUT = 1;private static final int    SHOW_PROGRESS = 2;private boolean             mUseFastForward;private boolean             mFromXml;private boolean             mListenersSet;private View.OnClickListener mNextListener, mPrevListener;StringBuilder               mFormatBuilder;Formatter                   mFormatter;private ImageButton         mPauseButton;private ImageButton         mFfwdButton;private ImageButton         mRewButton;private ImageButton         mNextButton;private ImageButton         mPrevButton;public MediaController(Context context, AttributeSet attrs) {super(context, attrs);mRoot = this;mContext = context;mUseFastForward = true;mFromXml = true;}@Overridepublic void onFinishInflate() {if (mRoot != null)initControllerView(mRoot);}public MediaController(Context context, boolean useFastForward) {super(context);mContext = context;mUseFastForward = useFastForward;initFloatingWindowLayout();initFloatingWindow();}public MediaController(Context context) {this(context, true);}private void initFloatingWindow() {mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);mWindow = PolicyManager.makeNewWindow(mContext);mWindow.setWindowManager(mWindowManager, null, null);mWindow.requestFeature(Window.FEATURE_NO_TITLE);mDecor = mWindow.getDecorView();mDecor.setOnTouchListener(mTouchListener);mWindow.setContentView(this);mWindow.setBackgroundDrawableResource(android.R.color.transparent);mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);setFocusable(true);setFocusableInTouchMode(true);setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);requestFocus();}private void initFloatingWindowLayout() {mDecorLayoutParams = new WindowManager.LayoutParams();WindowManager.LayoutParams p = mDecorLayoutParams;p.gravity = Gravity.TOP;p.height = LayoutParams.WRAP_CONTENT;p.x = 0;p.format = PixelFormat.TRANSLUCENT;p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;p.token = null;p.windowAnimations = 0; // android.R.style.DropDownAnimationDown;}private void updateFloatingWindowLayout() {int [] anchorPos = new int[2];mAnchor.getLocationOnScreen(anchorPos);WindowManager.LayoutParams p = mDecorLayoutParams;p.width = mAnchor.getWidth();p.y = anchorPos[1] + mAnchor.getHeight();}// This is called whenever mAnchor's layout bound changesprivate OnLayoutChangeListener mLayoutChangeListener =new OnLayoutChangeListener() {public void onLayoutChange(View v, int left, int top, int right,int bottom, int oldLeft, int oldTop, int oldRight,int oldBottom) {updateFloatingWindowLayout();if (mShowing) {mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams);}}};private OnTouchListener mTouchListener = new OnTouchListener() {public boolean onTouch(View v, MotionEvent event) {if (event.getAction() == MotionEvent.ACTION_DOWN) {if (mShowing) {hide();}}return false;}};public void setMediaPlayer(MediaPlayerControl player) {mPlayer = player;updatePausePlay();}/*** Set the view that acts as the anchor for the control view.* This can for example be a VideoView, or your Activity's main view.* @param view The view to which to anchor the controller when it is visible.*/public void setAnchorView(View view) {if (mAnchor != null) {mAnchor.removeOnLayoutChangeListener(mLayoutChangeListener);}mAnchor = view;if (mAnchor != null) {mAnchor.addOnLayoutChangeListener(mLayoutChangeListener);}FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);removeAllViews();View v = makeControllerView();addView(v, frameParams);}/*** Create the view that holds the widgets that control playback.* Derived classes can override this to create their own.* @return The controller view.* @hide This doesn't work as advertised*/protected View makeControllerView() {LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);mRoot = inflate.inflate(R.layout.media_controller, null);initControllerView(mRoot);return mRoot;}private void initControllerView(View v) {mPauseButton = (ImageButton) v.findViewById(R.id.pause);if (mPauseButton != null) {mPauseButton.requestFocus();mPauseButton.setOnClickListener(mPauseListener);}mFfwdButton = (ImageButton) v.findViewById(R.id.ffwd);if (mFfwdButton != null) {Log.v("MediaController", "into the mFfwdButton.setOnClickListener");mFfwdButton.setOnClickListener(mFfwdListener);if (!mFromXml) {mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);}}mRewButton = (ImageButton) v.findViewById(R.id.rew);if (mRewButton != null) {Log.v("MediaController", "into the mRewButton.setOnClickListener");mRewButton.setOnClickListener(mRewListener);if (!mFromXml) {mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);}}// By default these are hidden. They will be enabled when setPrevNextListeners() is called mNextButton = (ImageButton) v.findViewById(R.id.next);if (mNextButton != null && !mFromXml && !mListenersSet) {mNextButton.setVisibility(View.GONE);}mPrevButton = (ImageButton) v.findViewById(R.id.prev);if (mPrevButton != null && !mFromXml && !mListenersSet) {mPrevButton.setVisibility(View.GONE);}mProgress = (ProgressBar) v.findViewById(R.id.mediacontroller_progress);if (mProgress != null) {if (mProgress instanceof SeekBar) {SeekBar seeker = (SeekBar) mProgress;seeker.setOnSeekBarChangeListener(mSeekListener);}mProgress.setMax(1000);}mEndTime = (TextView) v.findViewById(R.id.time);mCurrentTime = (TextView) v.findViewById(R.id.time_current);mFormatBuilder = new StringBuilder();mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());installPrevNextListeners();}/*** Show the controller on screen. It will go away* automatically after 3 seconds of inactivity.*/public void show() {show(sDefaultTimeout);}/*** Disable pause or seek buttons if the stream cannot be paused or seeked.* This requires the control interface to be a MediaPlayerControlExt*/private void disableUnsupportedButtons() {try {if (mPauseButton != null && !mPlayer.canPause()) {mPauseButton.setEnabled(false);}if (mRewButton != null && !mPlayer.canSeekBackward()) {mRewButton.setEnabled(false);}if (mFfwdButton != null && !mPlayer.canSeekForward()) {mFfwdButton.setEnabled(false);}} catch (IncompatibleClassChangeError ex) {}}/*** Show the controller on screen. It will go away* automatically after 'timeout' milliseconds of inactivity.* @param timeout The timeout in milliseconds. Use 0 to show* the controller until hide() is called.*/public void show(int timeout) {if (!mShowing && mAnchor != null) {setProgress();if (mPauseButton != null) {mPauseButton.requestFocus();}disableUnsupportedButtons();updateFloatingWindowLayout();mWindowManager.addView(mDecor, mDecorLayoutParams);mShowing = true;}updatePausePlay();mHandler.sendEmptyMessage(SHOW_PROGRESS);Message msg = mHandler.obtainMessage(FADE_OUT);if (timeout != 0) {mHandler.removeMessages(FADE_OUT);mHandler.sendMessageDelayed(msg, timeout);}}public boolean isShowing() {return mShowing;}/*** Remove the controller from the screen.*/public void hide() {if (mAnchor == null)return;if (mShowing) {try {mHandler.removeMessages(SHOW_PROGRESS);mWindowManager.removeView(mDecor);} catch (IllegalArgumentException ex) {Log.w("MediaController", "already removed");}mShowing = false;}}private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {int pos;switch (msg.what) {case FADE_OUT:hide();break;case SHOW_PROGRESS:pos = setProgress();if (!mDragging && mShowing && mPlayer.isPlaying()) {msg = obtainMessage(SHOW_PROGRESS);sendMessageDelayed(msg, 1000 - (pos % 1000));}break;}}};private String stringForTime(int timeMs) {int totalSeconds = timeMs / 1000;int seconds = totalSeconds % 60;int minutes = (totalSeconds / 60) % 60;int hours   = totalSeconds / 3600;mFormatBuilder.setLength(0);if (hours > 0) {return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();} else {return mFormatter.format("%02d:%02d", minutes, seconds).toString();}}private int setProgress() {if (mPlayer == null || mDragging) {return 0;}int position = mPlayer.getCurrentPosition();int duration = mPlayer.getDuration();if (mProgress != null) {if (duration > 0) {// use long to avoid overflowlong pos = 1000L * position / duration;mProgress.setProgress( (int) pos);}int percent = mPlayer.getBufferPercentage();mProgress.setSecondaryProgress(percent * 10);}if (mEndTime != null)mEndTime.setText(stringForTime(duration));if (mCurrentTime != null)mCurrentTime.setText(stringForTime(position));return position;}@Overridepublic boolean onTouchEvent(MotionEvent event) {show(sDefaultTimeout);return true;}@Overridepublic boolean onTrackballEvent(MotionEvent ev) {show(sDefaultTimeout);return false;}@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {int keyCode = event.getKeyCode();final boolean uniqueDown = event.getRepeatCount() == 0&& event.getAction() == KeyEvent.ACTION_DOWN;if (keyCode ==  KeyEvent.KEYCODE_HEADSETHOOK|| keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE|| keyCode == KeyEvent.KEYCODE_SPACE) {if (uniqueDown) {doPauseResume();show(sDefaultTimeout);if (mPauseButton != null) {mPauseButton.requestFocus();}}return true;} else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {if (uniqueDown && !mPlayer.isPlaying()) {mPlayer.start();updatePausePlay();show(sDefaultTimeout);}return true;} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP|| keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {if (uniqueDown && mPlayer.isPlaying()) {mPlayer.pause();updatePausePlay();show(sDefaultTimeout);}return true;} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN|| keyCode == KeyEvent.KEYCODE_VOLUME_UP|| keyCode == KeyEvent.KEYCODE_VOLUME_MUTE|| keyCode == KeyEvent.KEYCODE_CAMERA) {// don't show the controls for volume adjustmentreturn super.dispatchKeyEvent(event);} else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {if (uniqueDown) {hide();}return true;}show(sDefaultTimeout);return super.dispatchKeyEvent(event);}private View.OnClickListener mPauseListener = new View.OnClickListener() {public void onClick(View v) {Log.v("MediaController", "into the mPauseListener");doPauseResume();show(sDefaultTimeout);}};private void updatePausePlay() {if (mRoot == null || mPauseButton == null)return;if (mPlayer.isPlaying()) {mPauseButton.setImageResource(R.drawable.media_pause);} else {mPauseButton.setImageResource(R.drawable.media_play);}}private void doPauseResume() {if (mPlayer.isPlaying()) {mPlayer.pause();} else {mPlayer.start();}updatePausePlay();}private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {public void onStartTrackingTouch(SeekBar bar) {show(3600000);mDragging = true;mHandler.removeMessages(SHOW_PROGRESS);}public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {if (!fromuser) {// We're not interested in programmatically generated changes to// the progress bar's position.return;}long duration = mPlayer.getDuration();long newposition = (duration * progress) / 1000L;mPlayer.seekTo( (int) newposition);if (mCurrentTime != null)mCurrentTime.setText(stringForTime( (int) newposition));}public void onStopTrackingTouch(SeekBar bar) {mDragging = false;setProgress();updatePausePlay();show(sDefaultTimeout);mHandler.sendEmptyMessage(SHOW_PROGRESS);}};@Overridepublic void setEnabled(boolean enabled) {if (mPauseButton != null) {mPauseButton.setEnabled(enabled);}if (mFfwdButton != null) {mFfwdButton.setEnabled(enabled);}if (mRewButton != null) {mRewButton.setEnabled(enabled);}if (mNextButton != null) {mNextButton.setEnabled(enabled && mNextListener != null);}if (mPrevButton != null) {mPrevButton.setEnabled(enabled && mPrevListener != null);}if (mProgress != null) {mProgress.setEnabled(enabled);}disableUnsupportedButtons();super.setEnabled(enabled);}@Overridepublic void onInitializeAccessibilityEvent(AccessibilityEvent event) {super.onInitializeAccessibilityEvent(event);event.setClassName(MediaController.class.getName());}@Overridepublic void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {super.onInitializeAccessibilityNodeInfo(info);info.setClassName(MediaController.class.getName());}private View.OnClickListener mRewListener = new View.OnClickListener() {public void onClick(View v) {Log.v("MediaController", "into the mRew button");int pos = mPlayer.getCurrentPosition();pos -= 5000; // millisecondsmPlayer.seekTo(pos);setProgress();show(sDefaultTimeout);}};private View.OnClickListener mFfwdListener = new View.OnClickListener() {public void onClick(View v) {Log.v("MediaController", "into the mFfw button");int pos = mPlayer.getCurrentPosition();pos += 15000; // millisecondsmPlayer.seekTo(pos);setProgress();show(sDefaultTimeout);}};private void installPrevNextListeners() {if (mNextButton != null) {mNextButton.setOnClickListener(mNextListener);mNextButton.setEnabled(mNextListener != null);}if (mPrevButton != null) {mPrevButton.setOnClickListener(mPrevListener);mPrevButton.setEnabled(mPrevListener != null);}}public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {mNextListener = next;mPrevListener = prev;mListenersSet = true;if (mRoot != null) {installPrevNextListeners();if (mNextButton != null && !mFromXml) {mNextButton.setVisibility(View.VISIBLE);}if (mPrevButton != null && !mFromXml) {mPrevButton.setVisibility(View.VISIBLE);}}}public interface MediaPlayerControl {void    start();void    pause();int     getDuration();int     getCurrentPosition();void    seekTo(int pos);boolean isPlaying();int     getBufferPercentage();boolean canPause();boolean canSeekBackward();boolean canSeekForward();}
}
直接应用源代码会提示很多错误,比如资源找不到等,其中有一个layout.media_controller文件,布局如下

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#CC000000"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:paddingTop="4dip"android:orientation="horizontal"><ImageButton android:id="@+id/prev" style="@style/MediaButton.Previous" /><ImageButton android:id="@+id/rew" style="@style/MediaButton.Rew" /><ImageButton android:id="@+id/pause" style="@style/MediaButton.Play" /><ImageButton android:id="@+id/ffwd" style="@style/MediaButton.Ffwd" /><ImageButton android:id="@+id/next" style="@style/MediaButton.Next" /> </LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><TextView android:id="@+id/time_current"android:textSize="14sp"android:textStyle="bold"android:paddingTop="4dip"android:paddingStart="4dip"android:layout_gravity="center_horizontal"android:layout_width="wrap_content"android:layout_height="wrap_content"android:paddingEnd="4dip"android:textColor="@color/dim_foreground_dark" /><SeekBarandroid:id="@+id/mediacontroller_progress"style="?android:attr/progressBarStyleHorizontal"android:layout_width="0dip"android:layout_weight="1"android:layout_height="32dip"android:layout_alignParentStart="true"android:layout_alignParentEnd="true" /><TextView android:id="@+id/time"android:textSize="14sp"android:textStyle="bold"android:paddingTop="4dip"android:paddingEnd="4dip"android:layout_gravity="center_horizontal"android:layout_width="wrap_content"android:layout_height="wrap_content"android:paddingStart="4dip"android:textColor="@color/dim_foreground_dark" /></LinearLayout></LinearLayout>

其中有一些style风格如下:

<style name="MediaButton"><item name="android:background">@null</item><item name="android:layout_width">71dip</item><item name="android:layout_height">52dip</item></style><style name="MediaButton.Previous"><item name="android:src">@android:drawable/ic_media_previous</item></style><style name="MediaButton.Next"><item name="android:src">@android:drawable/ic_media_next</item></style><style name="MediaButton.Play"><item name="android:src">@android:drawable/ic_media_play</item></style><style name="MediaButton.Ffwd"><item name="android:src">@android:drawable/ic_media_ff</item></style><style name="MediaButton.Rew"><item name="android:src">@android:drawable/ic_media_rew</item></style><style name="MediaButton.Pause"><item name="android:src">@android:drawable/ic_media_pause</item></style>
可以根据其中的UI更换成自己的风格。

最棘手的是下面几段代码:

mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);mWindow = PolicyManager.makeNewWindow(mContext);mWindow.setWindowManager(mWindowManager, null, null);mWindow.requestFeature(Window.FEATURE_NO_TITLE);mDecor = mWindow.getDecorView();mDecor.setOnTouchListener(mTouchListener);mWindow.setContentView(this);mWindow.setBackgroundDrawableResource(android.R.color.transparent);
PolicyManager.makeNewWindow是一个内部类,用于新建一个window窗口,该类的包为:

import com.android.internal.policy.PolicyManager;

这个包为android的core层使用的,应用程序无法调用。由于对这些函数不胜了解,到现在我任然没有搞懂这段代码用什么替换,如果有哪位大神知道,可以告知我,本人不胜感激。

我这地方用了另一种方法搞定编译,PolicyManager.makeNewWindow这个函数android内部是有的,位于system/framwork/framwork.odex中,但是在eclipse中的android的jar包中找不到这个类,所以一种方法是将这个类的jar包直接加入到工程当中,经过网络搜索,捣鼓一通以后重新得到了一个包含com.android.internal的jar包。


下载地址:

http://download.csdn.net/detail/taoxugang2012/6509377

由于时间倡促,技术不够强硬,这个东西暂时就这么做,严格上来说,应该用应用层的接口方法来替换该部分代码。


到此,有了自定义的videoview和自定义MediaController,那么一个播放器也就差不多了,比起自己重零做起,android的这两个类自然有它的好处,使用的人可以根据这个类修改其中的UI界面,对其它部分进行优化。

现在就这么做吧,等我技术知识储备好了,再重新写一个更完美的。

这篇关于关于MediaController的自定义的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/821957

相关文章

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

如何自定义Nginx JSON日志格式配置

《如何自定义NginxJSON日志格式配置》Nginx作为最流行的Web服务器之一,其灵活的日志配置能力允许我们根据需求定制日志格式,本文将详细介绍如何配置Nginx以JSON格式记录访问日志,这种... 目录前言为什么选择jsON格式日志?配置步骤详解1. 安装Nginx服务2. 自定义JSON日志格式各

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

基于Spring实现自定义错误信息返回详解

《基于Spring实现自定义错误信息返回详解》这篇文章主要为大家详细介绍了如何基于Spring实现自定义错误信息返回效果,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录背景目标实现产出背景Spring 提供了 @RestConChina编程trollerAdvice 用来实现 HTT

SpringSecurity 认证、注销、权限控制功能(注销、记住密码、自定义登入页)

《SpringSecurity认证、注销、权限控制功能(注销、记住密码、自定义登入页)》SpringSecurity是一个强大的Java框架,用于保护应用程序的安全性,它提供了一套全面的安全解决方案... 目录简介认识Spring Security“认证”(Authentication)“授权” (Auth

SpringBoot自定义注解如何解决公共字段填充问题

《SpringBoot自定义注解如何解决公共字段填充问题》本文介绍了在系统开发中,如何使用AOP切面编程实现公共字段自动填充的功能,从而简化代码,通过自定义注解和切面类,可以统一处理创建时间和修改时间... 目录1.1 问题分析1.2 实现思路1.3 代码开发1.3.1 步骤一1.3.2 步骤二1.3.3

dubbo3 filter(过滤器)如何自定义过滤器

《dubbo3filter(过滤器)如何自定义过滤器》dubbo3filter(过滤器)类似于javaweb中的filter和springmvc中的intercaptor,用于在请求发送前或到达前进... 目录dubbo3 filter(过滤器)简介dubbo 过滤器运行时机自定义 filter第一种 @A

CSS自定义浏览器滚动条样式完整代码

《CSS自定义浏览器滚动条样式完整代码》:本文主要介绍了如何使用CSS自定义浏览器滚动条的样式,包括隐藏滚动条的角落、设置滚动条的基本样式、轨道样式和滑块样式,并提供了完整的CSS代码示例,通过这些技巧,你可以为你的网站添加个性化的滚动条样式,从而提升用户体验,详细内容请阅读本文,希望能对你有所帮助...

SpringBoot 自定义消息转换器使用详解

《SpringBoot自定义消息转换器使用详解》本文详细介绍了SpringBoot消息转换器的知识,并通过案例操作演示了如何进行自定义消息转换器的定制开发和使用,感兴趣的朋友一起看看吧... 目录一、前言二、SpringBoot 内容协商介绍2.1 什么是内容协商2.2 内容协商机制深入理解2.2.1 内容

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06