AndroidSideMenu能够让你轻而易举地创建侧滑菜单。需要注意的是,该项目自身并不提供任何创建菜单的工具,因此,开发者可以自由创建内部菜单。
核心类如下:
/*
* Copyright dmitry.zaicew@gmail.com Dmitry Zaitsev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.agimind.widget;
import java.util.LinkedList;
import java.util.Queue;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.FrameLayout;
public class SlideHolder extends FrameLayout {
public final static int DIRECTION_LEFT = 1;
public final static int DIRECTION_RIGHT = -1;
protected final static int MODE_READY = 0;
protected final static int MODE_SLIDE = 1;
protected final static int MODE_FINISHED = 2;
private Bitmap mCachedBitmap;
private Canvas mCachedCanvas;
private Paint mCachedPaint;
private View mMenuView;
private int mMode = MODE_READY;
private int mDirection = DIRECTION_LEFT;
private int mOffset = 0;
private int mStartOffset;
private int mEndOffset;
private boolean mEnabled = true;
private boolean mInterceptTouch = true;
private boolean mAlwaysOpened = false;
private boolean mDispatchWhenOpened = false;
private Queue<Runnable> mWhenReady = new LinkedList<Runnable>();
private OnSlideListener mListener;
public SlideHolder(Context context) {
super(context);
initView();
}
public SlideHolder(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SlideHolder(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
mCachedPaint = new Paint(
Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG
| Paint.DITHER_FLAG
);
}
@Override
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
@Override
public boolean isEnabled() {
return mEnabled;
}
/**
*
* @param direction - direction in which SlideHolder opens. Can be: DIRECTION_LEFT, DIRECTION_RIGHT
*/
public void setDirection(int direction) {
closeImmediately();
mDirection = direction;
}
/**
*
* @param allow - if false, SlideHolder won't react to swiping gestures (but still will be able to work by manually invoking mathods)
*/
public void setAllowInterceptTouch(boolean allow) {
mInterceptTouch = allow;
}
public boolean isAllowedInterceptTouch() {
return mInterceptTouch;
}
/**
*
* @param dispatch - if true, in open state SlideHolder will dispatch touch events to main layout (in other words - it will be clickable)
*/
public void setDispatchTouchWhenOpened(boolean dispatch) {
mDispatchWhenOpened = dispatch;
}
public boolean isDispatchTouchWhenOpened() {
return mDispatchWhenOpened;
}
/**
*
* @param opened - if true, SlideHolder will always be in opened state (which means that swiping won't work)
*/
public void setAlwaysOpened(boolean opened) {
mAlwaysOpened = opened;
requestLayout();
}
public int getMenuOffset() {
return mOffset;
}
public void setOnSlideListener(OnSlideListener lis) {
mListener = lis;
}
public boolean isOpened() {
return mAlwaysOpened || mMode == MODE_FINISHED;
}
public void toggle(boolean immediately) {
if(immediately) {
toggleImmediately();
} else {
toggle();
}
}
public void toggle() {
if(isOpened()) {
close();
} else {
open();
}
}
public void toggleImmediately() {
if(isOpened()) {
closeImmediately();
} else {
openImmediately();
}
}
public boolean open() {
if(isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {
return false;
}
if(!isReadyForSlide()) {
mWhenReady.add(new Runnable() {
@Override
public void run() {
open();
}
});
return true;
}
initSlideMode();
Animation anim = new SlideAnimation(mOffset, mEndOffset);
anim.setAnimationListener(mOpenListener);
startAnimation(anim);
invalidate();
return true;
}
public boolean openImmediately() {
if(isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {
return false;
}
if(!isReadyForSlide()) {
mWhenReady.add(new Runnable() {
@Override
public void run() {
openImmediately();
}
});
return true;
}
mMenuView.setVisibility(View.VISIBLE);
mMode = MODE_FINISHED;
requestLayout();
if(mListener != null) {
mListener.onSlideCompleted(true);
}
return true;
}
public boolean close() {
if(!isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {
return false;
}
if(!isReadyForSlide()) {
mWhenReady.add(new Runnable() {
@Override
public void run() {
close();
}
});
return true;
}
initSlideMode();
Animation anim = new SlideAnimation(mOffset, mEndOffset);
anim.setAnimationListener(mCloseListener);
startAnimation(anim);
invalidate();
return true;
}
public boolean closeImmediately() {
if(!isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {
return false;
}
if(!isReadyForSlide()) {
mWhenReady.add(new Runnable() {
@Override
public void run() {
closeImmediately();
}
});
return true;
}
mMenuView.setVisibility(View.GONE);
mMode = MODE_READY;
requestLayout();
if(mListener != null) {
mListener.onSlideCompleted(false);
}
return true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int parentLeft = 0;
final int parentTop = 0;
final int parentRight = r - l;
final int parentBottom = b - t;
View menu = getChildAt(0);
int menuWidth = menu.getMeasuredWidth();
if(mDirection == DIRECTION_LEFT) {
menu.layout(parentLeft, parentTop, parentLeft+menuWidth, parentBottom);
} else {
menu.layout(parentRight-menuWidth, parentTop, parentRight, parentBottom);
}
if(mAlwaysOpened) {
if(mDirection == DIRECTION_LEFT) {
mOffset = menuWidth;
} else {
mOffset = 0;
}
} else if(mMode == MODE_FINISHED) {
mOffset = mDirection*menuWidth;
} else if(mMode == MODE_READY) {
mOffset = 0;
}
View main = getChildAt(1);
main.layout(
parentLeft + mOffset,
parentTop,
parentLeft + mOffset + main.getMeasuredWidth(),
parentBottom
);
invalidate();
Runnable rn;
while((rn = mWhenReady.poll()) != null) {
rn.run();
}
}
private boolean isReadyForSlide() {
return (getWidth() > 0 && getHeight() > 0);
}
@Override
protected void onMeasure(int wSp, int hSp) {
mMenuView = getChildAt(0);
if(mAlwaysOpened) {
View main = getChildAt(1);
if(mMenuView != null && main != null) {
measureChild(mMenuView, wSp, hSp);
LayoutParams lp = (LayoutParams) main.getLayoutParams();
if(mDirection == DIRECTION_LEFT) {
lp.leftMargin = mMenuView.getMeasuredWidth();
} else {
lp.rightMargin = mMenuView.getMeasuredWidth();
}
}
}
super.onMeasure(wSp, hSp);
}
private byte mFrame = 0;
@Override
protected void dispatchDraw(Canvas canvas) {
try {
if(mMode == MODE_SLIDE) {
View main = getChildAt(1);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
/*
* On new versions we redrawing main layout only
* if it's marked as dirty
*/
if(main.isDirty()) {
mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
main.draw(mCachedCanvas);
}
} else {
/*
* On older versions we just redrawing our cache
* every 5th frame
*/
if(++mFrame % 5 == 0) {
mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
main.draw(mCachedCanvas);
}
}
/*
* Draw only visible part of menu
*/
View menu = getChildAt(0);
final int scrollX = menu.getScrollX();
final int scrollY = menu.getScrollY();
canvas.save();
if(mDirection == DIRECTION_LEFT) {
canvas.clipRect(0, 0, mOffset, menu.getHeight(), Op.REPLACE);
} else {
int menuWidth = menu.getWidth();
int menuLeft = menu.getLeft();
canvas.clipRect(menuLeft+menuWidth+mOffset, 0, menuLeft+menuWidth, menu.getHeight());
}
canvas.translate(menu.getLeft(), menu.getTop());
canvas.translate(-scrollX, -scrollY);
menu.draw(canvas);
canvas.restore();
canvas.drawBitmap(mCachedBitmap, mOffset, 0, mCachedPaint);
} else {
if(!mAlwaysOpened && mMode == MODE_READY) {
mMenuView.setVisibility(View.GONE);
}
super.dispatchDraw(canvas);
}
} catch(IndexOutOfBoundsException e) {
/*
* Possibility of crashes on some devices (especially on Samsung).
* Usually, when ListView is empty.
*/
}
}
private int mHistoricalX = 0;
private boolean mCloseOnRelease = false;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(((!mEnabled || !mInterceptTouch) && mMode == MODE_READY) || mAlwaysOpened) {
return super.dispatchTouchEvent(ev);
}
if(mMode != MODE_FINISHED) {
onTouchEvent(ev);
if(mMode != MODE_SLIDE) {
super.dispatchTouchEvent(ev);
} else {
MotionEvent cancelEvent = MotionEvent.obtain(ev);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(cancelEvent);
cancelEvent.recycle();
}
return true;
} else {
final int action = ev.getAction();
Rect rect = new Rect();
View menu = getChildAt(0);
menu.getHitRect(rect);
if(!rect.contains((int) ev.getX(), (int) ev.getY())) {
if (action == MotionEvent.ACTION_UP && mCloseOnRelease && !mDispatchWhenOpened) {
close();
mCloseOnRelease = false;
} else {
if(action == MotionEvent.ACTION_DOWN && !mDispatchWhenOpened) {
mCloseOnRelease = true;
}
onTouchEvent(ev);
}
if(mDispatchWhenOpened) {
super.dispatchTouchEvent(ev);
}
return true;
} else {
onTouchEvent(ev);
ev.offsetLocation(-menu.getLeft(), -menu.getTop());
menu.dispatchTouchEvent(ev);
return true;
}
}
}
private boolean handleTouchEvent(MotionEvent ev) {
if(!mEnabled) {
return false;
}
float x = ev.getX();
if(ev.getAction() == MotionEvent.ACTION_DOWN) {
mHistoricalX = (int) x;
return true;
}
if(ev.getAction() == MotionEvent.ACTION_MOVE) {
float diff = x - mHistoricalX;
if((mDirection*diff > 50 && mMode == MODE_READY) || (mDirection*diff < -50 && mMode == MODE_FINISHED)) {
mHistoricalX = (int) x;
initSlideMode();
} else if(mMode == MODE_SLIDE) {
mOffset += diff;
mHistoricalX = (int) x;
if(!isSlideAllowed()) {
finishSlide();
}
} else {
return false;
}
}
if(ev.getAction() == MotionEvent.ACTION_UP) {
if(mMode == MODE_SLIDE) {
finishSlide();
}
mCloseOnRelease = false;
return false;
}
return mMode == MODE_SLIDE;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean handled = handleTouchEvent(ev);
invalidate();
return handled;
}
private void initSlideMode() {
mCloseOnRelease = false;
View v = getChildAt(1);
if(mMode == MODE_READY) {
mStartOffset = 0;
mEndOffset = mDirection*getChildAt(0).getWidth();
} else {
mStartOffset = mDirection*getChildAt(0).getWidth();
mEndOffset = 0;
}
mOffset = mStartOffset;
if(mCachedBitmap == null || mCachedBitmap.isRecycled() || mCachedBitmap.getWidth() != v.getWidth()) {
mCachedBitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
mCachedCanvas = new Canvas(mCachedBitmap);
} else {
mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
}
v.setVisibility(View.VISIBLE);
mCachedCanvas.translate(-v.getScrollX(), -v.getScrollY());
v.draw(mCachedCanvas);
mMode = MODE_SLIDE;
mMenuView.setVisibility(View.VISIBLE);
}
private boolean isSlideAllowed() {
return (mDirection*mEndOffset > 0 && mDirection*mOffset < mDirection*mEndOffset && mDirection*mOffset >= mDirection*mStartOffset)
|| (mEndOffset == 0 && mDirection*mOffset > mDirection*mEndOffset && mDirection*mOffset <= mDirection*mStartOffset);
}
private void completeOpening() {
mOffset = mDirection*mMenuView.getWidth();
requestLayout();
post(new Runnable() {
@Override
public void run() {
mMode = MODE_FINISHED;
mMenuView.setVisibility(View.VISIBLE);
}
});
if(mListener != null) {
mListener.onSlideCompleted(true);
}
}
private Animation.AnimationListener mOpenListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
completeOpening();
}
};
private void completeClosing() {
mOffset = 0;
requestLayout();
post(new Runnable() {
@Override
public void run() {
mMode = MODE_READY;
mMenuView.setVisibility(View.GONE);
}
});
if(mListener != null) {
mListener.onSlideCompleted(false);
}
}
private Animation.AnimationListener mCloseListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
completeClosing();
}
};
private void finishSlide() {
if(mDirection*mEndOffset > 0) {
if(mDirection*mOffset > mDirection*mEndOffset/2) {
if(mDirection*mOffset > mDirection*mEndOffset) mOffset = mEndOffset;
Animation anim = new SlideAnimation(mOffset, mEndOffset);
anim.setAnimationListener(mOpenListener);
startAnimation(anim);
} else {
if(mDirection*mOffset < mDirection*mStartOffset) mOffset = mStartOffset;
Animation anim = new SlideAnimation(mOffset, mStartOffset);
anim.setAnimationListener(mCloseListener);
startAnimation(anim);
}
} else {
if(mDirection*mOffset < mDirection*mStartOffset/2) {
if(mDirection*mOffset < mDirection*mEndOffset) mOffset = mEndOffset;
Animation anim = new SlideAnimation(mOffset, mEndOffset);
anim.setAnimationListener(mCloseListener);
startAnimation(anim);
} else {
if(mDirection*mOffset > mDirection*mStartOffset) mOffset = mStartOffset;
Animation anim = new SlideAnimation(mOffset, mStartOffset);
anim.setAnimationListener(mOpenListener);
startAnimation(anim);
}
}
}
private class SlideAnimation extends Animation {
private static final float SPEED = 0.6f;
private float mStart;
private float mEnd;
public SlideAnimation(float fromX, float toX) {
mStart = fromX;
mEnd = toX;
setInterpolator(new DecelerateInterpolator());
float duration = Math.abs(mEnd - mStart) / SPEED;
setDuration((long) duration);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
float offset = (mEnd - mStart) * interpolatedTime + mStart;
mOffset = (int) offset;
postInvalidate();
}
}
public static interface OnSlideListener {
public void onSlideCompleted(boolean opened);
}
}
使用:
package com.agimind.sidemenuexample;
import com.agimind.widget.SlideHolder;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.app.ActionBar;
import android.app.Activity;
public class MainActivity extends Activity {
private SlideHolder mSlideHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSlideHolder = (SlideHolder) findViewById(R.id.slideHolder);
// mSlideHolder.setAllowInterceptTouch(false);
// mSlideHolder.setAlwaysOpened(true);
/*
* toggleView can actually be any view you want. Here, for simplicity,
* we're using TextView, but you can easily replace it with button.
*
* Note, when menu opens our textView will become invisible, so it quite
* pointless to assign toggle-event to it. In real app consider using UP
* button instead. In our case toggle() can be replaced with open().
*/
ActionBar actionBar = getActionBar();
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setHomeButtonEnabled(true);
View toggleView = findViewById(R.id.textView);
toggleView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSlideHolder.toggle();
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
mSlideHolder.toggle();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
}
布局如下:
<com.agimind.widget.SlideHolder xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/slideHolder"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".MainActivity" >
<ScrollView
android:layout_width="200dp"
android:layout_height="fill_parent"
android:background="@android:color/black" >
<LinearLayout
android:layout_width="200dp"
android:layout_height="wrap_content"
android:orientation="vertical" >
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
</LinearLayout>
</ScrollView>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/swipe"
android:textSize="25sp" />
</RelativeLayout>
</com.agimind.widget.SlideHolder>
下载:AndroidSideMenu
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# Android
# AndroidSideMenu
# 抽屉菜单
# 侧滑菜单
# Android 侧滑抽屉菜单的实现代码
# Android 抽屉效果的导航菜单实现代码实例
# Android实现自定义滑动式抽屉菜单效果
# Android App中DrawerLayout抽屉效果的菜单编写实例
# Android组件之DrawerLayout实现抽屉菜单
# Android开发实现抽屉菜单
# 的是
# 让你
# 轻而易举
# 该项目
# 需要注意
# 大家多多
# Override
# DITHER_FLAG
# setEnabled
# isEnabled
# enabled
# FILTER_BITMAP_FLAG
# attrs
# initView
# defStyle
# ANTI_ALIAS_FLAG
# void
# return
# gestures
# swiping
相关文章:
如何确保FTP站点访问权限与数据传输安全?
建站主机助手选型指南:2025年热门推荐与高效部署技巧
小型网站制作HTML,*游戏网站怎么搭建?
广州营销型建站服务商推荐:技术优势与SEO优化解析
如何快速使用云服务器搭建个人网站?
C++中的Pimpl idiom是什么,有什么好处?(隐藏实现)
如何快速搭建响应式可视化网站?
建站之星CMS五站合一模板配置与SEO优化指南
美食网站链接制作教程视频,哪个教做美食的网站比较专业点?
寿县云建站:智能SEO优化与多行业模板快速上线指南
如何通过VPS搭建网站快速盈利?
电商平台网站制作流程,电商网站如何制作?
动图在线制作网站有哪些,滑动动图图集怎么做?
网站代码制作软件有哪些,如何生成自己网站的代码?
建站主机SSH密钥生成步骤及常见问题解答?
网站制作多少钱一个,建一个论坛网站大约需要多少钱?
建站之星IIS配置教程:代码生成技巧与站点搭建指南
相亲简历制作网站推荐大全,新相亲大会主持人小萍萍资料?
佛山网站制作系统,佛山企业变更地址网上办理步骤?
网站制作公司排行榜,抖音怎样做个人官方网站
油猴 教程,油猴搜脚本为什么会网页无法显示?
网站制作服务平台,有什么网站可以发布本地服务信息?
如何选择香港主机高效搭建外贸独立站?
建站之星后台管理:高效配置与模板优化提升用户体验
深圳防火门网站制作公司,深圳中天明防火门怎么编码?
简单实现Android验证码
公司门户网站制作公司有哪些,怎样使用wordpress制作一个企业网站?
如何在阿里云ECS服务器部署织梦CMS网站?
如何快速配置高效服务器建站软件?
网站制作专业公司有哪些,如何制作一个企业网站,建设网站的基本步骤有哪些?
建站之星如何实现五合一智能建站与营销推广?
如何在宝塔面板中创建新站点?
如何在西部数码注册域名并快速搭建网站?
php json中文编码为null的解决办法
表情包在线制作网站免费,表情包怎么弄?
官网自助建站平台指南:在线制作、快速建站与模板选择全解析
济南网站制作的价格,历城一职专官方网站?
武汉网站如何制作,黄黄高铁武穴北站途经哪些村庄?
企业网站制作费用多少,企业网站空间一般需要多大,费用是多少?
手机怎么制作网站教程步骤,手机怎么做自己的网页链接?
标准网站视频模板制作软件,现在有哪个网站的视频编辑素材最齐全的,背景音乐、音效等?
建站主机选购指南:核心配置与性价比推荐解析
网站制作的软件有哪些,制作微信公众号除了秀米还有哪些比较好用的平台?
SAX解析器是什么,它与DOM在处理大型XML文件时有何不同?
南阳网站制作公司推荐,小学电子版试卷去哪里找资源好?
定制建站价位费用解析与套餐推荐全攻略
如何将凡科建站内容保存为本地文件?
广州顶尖建站服务:企业官网建设与SEO优化一体化方案
如何在橙子建站中快速调整背景颜色?
宝塔建站教程:一键部署配置流程与SEO优化实战指南
*请认真填写需求信息,我们会在24小时内与您取得联系。